summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--.sops.yaml6
-rw-r--r--_sources/generated.json128
-rw-r--r--_sources/generated.nix108
-rw-r--r--accounts/gkleen@eostre.nix8
-rw-r--r--accounts/gkleen@installer.nix8
-rw-r--r--accounts/gkleen@sif/default.nix381
-rw-r--r--accounts/gkleen@sif/emacs.el4
-rw-r--r--accounts/gkleen@sif/firefox-chrome.css30
-rw-r--r--accounts/gkleen@sif/niri/default.nix1305
-rw-r--r--accounts/gkleen@sif/niri/mako.nix60
-rw-r--r--accounts/gkleen@sif/niri/swayosd.nix66
-rw-r--r--accounts/gkleen@sif/niri/waybar.nix74
-rw-r--r--accounts/gkleen@sif/ssh-hosts.nix85
-rw-r--r--accounts/gkleen@sif/synadm/default.nix9
-rw-r--r--accounts/gkleen@sif/synadm/synadm_yaml15
-rw-r--r--accounts/gkleen@sif/systemd.nix123
-rw-r--r--accounts/gkleen@sif/utils/async-yt-dlp.nix57
-rw-r--r--accounts/gkleen@sif/utils/sieve-edit.nix24
-rw-r--r--accounts/gkleen@sif/zshrc149
-rw-r--r--accounts/gkleen@surtr.nix8
-rw-r--r--accounts/gkleen@vidhar.nix4
-rw-r--r--accounts/mherold@eostre.nix6
-rw-r--r--accounts/root@installer.nix8
-rw-r--r--accounts/root@sif.nix8
-rw-r--r--accounts/root@surtr.nix8
-rw-r--r--accounts/root@vidhar.nix9
-rw-r--r--flake.lock334
-rw-r--r--flake.nix60
-rw-r--r--home-modules/nixpkgs-release-check.nix4
-rw-r--r--home-modules/pandoc/default.nix27
-rw-r--r--home-modules/pandoc/german_abbreviations.txt1423
-rwxr-xr-xhome-modules/pandoc/german_abbreviations.txt.gup35
-rw-r--r--hosts/eostre/default.nix21
-rw-r--r--hosts/sif/default.nix100
-rw-r--r--hosts/sif/email/default.nix110
-rw-r--r--hosts/sif/email/relay.crt11
-rw-r--r--hosts/sif/email/relay.key19
-rw-r--r--hosts/sif/email/secrets.yaml (renamed from hosts/sif/mail/secrets.yaml)0
-rw-r--r--hosts/sif/greetd/default.nix5
-rw-r--r--hosts/sif/mail/default.nix70
-rw-r--r--hosts/sif/ruleset.nft8
-rw-r--r--hosts/surtr/audiobookshelf.nix66
-rw-r--r--hosts/surtr/bifrost/default.nix4
-rw-r--r--hosts/surtr/default.nix22
-rw-r--r--hosts/surtr/dns/Gupfile2
-rw-r--r--hosts/surtr/dns/default.nix8
-rw-r--r--hosts/surtr/dns/key.gup2
-rw-r--r--hosts/surtr/dns/keys/audiobookshelf.yggdrasil.li_acme19
-rw-r--r--hosts/surtr/dns/keys/hledger.yggdrasil.li_acme24
-rw-r--r--hosts/surtr/dns/keys/kimai.yggdrasil.li_acme19
-rw-r--r--hosts/surtr/dns/keys/paperless.yggdrasil.li_acme24
-rw-r--r--hosts/surtr/dns/zones/email.nights.soa8
-rw-r--r--hosts/surtr/dns/zones/li.141.soa11
-rw-r--r--hosts/surtr/dns/zones/li.kleen.soa9
-rw-r--r--hosts/surtr/dns/zones/li.synapse.soa2
-rw-r--r--hosts/surtr/dns/zones/li.xmpp.soa43
-rw-r--r--hosts/surtr/dns/zones/li.yggdrasil.soa39
-rw-r--r--hosts/surtr/dns/zones/org.dirty-haskell.soa34
-rw-r--r--hosts/surtr/dns/zones/org.praseodym.soa9
-rw-r--r--hosts/surtr/email/ccert-policy-server/ccert_policy_server/__main__.py16
-rw-r--r--hosts/surtr/email/default.nix219
-rw-r--r--hosts/surtr/email/internal-policy-server/.envrc4
-rw-r--r--hosts/surtr/email/internal-policy-server/.gitignore2
-rw-r--r--hosts/surtr/email/internal-policy-server/internal_policy_server/__init__.py0
-rw-r--r--hosts/surtr/email/internal-policy-server/internal_policy_server/__main__.py106
-rw-r--r--hosts/surtr/email/internal-policy-server/pyproject.toml18
-rw-r--r--hosts/surtr/email/internal-policy-server/uv.lock119
-rw-r--r--hosts/surtr/hledger.nix66
-rw-r--r--hosts/surtr/kimai.nix68
-rw-r--r--hosts/surtr/paperless.nix66
-rw-r--r--hosts/surtr/postgresql/default.nix62
-rw-r--r--hosts/surtr/tls/tsig_key.gup2
-rw-r--r--hosts/surtr/tls/tsig_keys/audiobookshelf.yggdrasil.li19
-rw-r--r--hosts/surtr/tls/tsig_keys/hledger.yggdrasil.li24
-rw-r--r--hosts/surtr/tls/tsig_keys/kimai.yggdrasil.li19
-rw-r--r--hosts/surtr/tls/tsig_keys/paperless.yggdrasil.li24
-rw-r--r--hosts/surtr/vpn/default.nix8
-rw-r--r--hosts/surtr/vpn/geri.pub2
-rw-r--r--hosts/surtr/zfs.nix2
-rw-r--r--hosts/vidhar/audiobookshelf/abs-podcast-autoplaylist-gkleen.toml19
-rw-r--r--hosts/vidhar/audiobookshelf/default.nix21
-rw-r--r--hosts/vidhar/default.nix4
-rw-r--r--hosts/vidhar/hledger/default.nix83
-rw-r--r--hosts/vidhar/hledger/htpasswd24
-rw-r--r--hosts/vidhar/kimai/default.nix89
-rw-r--r--hosts/vidhar/kimai/ruleset.nft149
-rw-r--r--hosts/vidhar/network/default.nix9
-rw-r--r--hosts/vidhar/network/dhcp/default.nix221
-rw-r--r--hosts/vidhar/network/ruleset.nft24
-rw-r--r--hosts/vidhar/paperless/default.nix25
-rw-r--r--hosts/vidhar/paperless/rootpw24
-rw-r--r--hosts/vidhar/prometheus/default.nix3
-rw-r--r--installer-profiles/cd-dvd.nix8
-rw-r--r--installer-profiles/netboot.nix5
-rw-r--r--installer-profiles/nfsroot.nix2
-rw-r--r--installer/default.nix8
-rw-r--r--lib/pythonSet.nix42
-rw-r--r--modules/abs-podcast-autoplaylist.nix55
-rw-r--r--modules/backup-utils.nix3
-rw-r--r--modules/borgcopy/.envrc4
-rw-r--r--modules/borgcopy/.gitignore2
-rw-r--r--modules/borgcopy/default.nix40
-rw-r--r--modules/borgcopy/poetry.lock180
-rw-r--r--modules/borgcopy/pyproject.toml33
-rw-r--r--modules/borgcopy/uv.lock146
-rw-r--r--modules/envfs.nix77
-rw-r--r--modules/i18n.nix156
-rw-r--r--modules/installer.nix56
-rw-r--r--modules/pgbackrest.nix3
-rw-r--r--modules/postsrsd.nix157
-rw-r--r--modules/systemd-run0.nix4
-rw-r--r--modules/tzupdate.nix81
-rw-r--r--modules/uucp.nix373
-rw-r--r--nvfetcher.toml14
-rw-r--r--overlays/abs-podcast-autoplaylist/.envrc4
-rw-r--r--overlays/abs-podcast-autoplaylist/.gitignore2
-rw-r--r--overlays/abs-podcast-autoplaylist/abs_podcast_autoplaylist/__init__.py0
-rw-r--r--overlays/abs-podcast-autoplaylist/abs_podcast_autoplaylist/__main__.py107
-rw-r--r--overlays/abs-podcast-autoplaylist/default.nix19
-rw-r--r--overlays/abs-podcast-autoplaylist/pyproject.toml16
-rw-r--r--overlays/abs-podcast-autoplaylist/uv.lock129
-rw-r--r--overlays/cake-prometheus-exporter/default.nix11
-rw-r--r--overlays/deploy-rs.nix16
-rw-r--r--overlays/etesync-dav.nix47
-rw-r--r--overlays/inwx-cdnskey/default.nix11
-rw-r--r--overlays/keepassxc/database-open-dialog.patch129
-rw-r--r--overlays/keepassxc/default.nix8
-rw-r--r--overlays/lesspipe.nix2
-rw-r--r--overlays/nftables-prometheus-exporter/default.nix11
-rw-r--r--overlays/niri.nix8
-rw-r--r--overlays/nix-direnv/default.nix53
-rw-r--r--overlays/nix-direnv/static-nix.patch62
-rw-r--r--overlays/nix-monitored.nix8
-rw-r--r--overlays/persistent-nix-shell/default.nix5
-rw-r--r--overlays/postsrsd.nix11
-rw-r--r--overlays/preserve-dscp/default.nix2
-rw-r--r--overlays/prometheus-lvm-exporter.nix2
-rw-r--r--overlays/scutiger.nix2
-rw-r--r--overlays/spice-record.nix2
-rw-r--r--overlays/swayosd/default.nix13
-rw-r--r--overlays/swayosd/exponential.patch57
-rw-r--r--overlays/uucp/default.nix9
-rw-r--r--overlays/uucp/mailprogram.patch16
-rw-r--r--overlays/waybar-systemd-inhibit/.envrc4
-rw-r--r--overlays/waybar-systemd-inhibit/.gitignore2
-rw-r--r--overlays/waybar-systemd-inhibit/default.nix20
-rw-r--r--overlays/waybar-systemd-inhibit/pyproject.toml17
-rw-r--r--overlays/waybar-systemd-inhibit/uv.lock102
-rw-r--r--overlays/waybar-systemd-inhibit/waybar_systemd_inhibit/__init__.py0
-rw-r--r--overlays/waybar-systemd-inhibit/waybar_systemd_inhibit/__main__.py117
-rw-r--r--overlays/waybar.nix11
-rw-r--r--overlays/worktime/.envrc4
-rw-r--r--overlays/worktime/.gitignore2
-rw-r--r--overlays/worktime/default.nix26
-rw-r--r--overlays/worktime/poetry.lock248
-rw-r--r--overlays/worktime/pyproject.toml41
-rw-r--r--overlays/worktime/uv.lock248
-rwxr-xr-xoverlays/worktime/worktime/__main__.py590
-rw-r--r--overlays/zte-prometheus-exporter/default.nix11
-rw-r--r--shell.nix16
-rw-r--r--system-profiles/core/default.nix34
-rw-r--r--system-profiles/default-locale.nix27
-rw-r--r--system-profiles/nfsroot.nix6
-rw-r--r--system-profiles/rebuild-machines/default.nix20
-rw-r--r--system-profiles/zfs.nix6
-rw-r--r--user-profiles/core.nix6
-rw-r--r--user-profiles/feeds/alot.config50
-rw-r--r--user-profiles/feeds/default.nix11
-rw-r--r--user-profiles/feeds/imm-notmuch-insert.py52
-rw-r--r--user-profiles/feeds/module.nix236
-rw-r--r--user-profiles/mpv/default.nix2
-rw-r--r--user-profiles/tmux/default.nix10
-rw-r--r--user-profiles/tmux/tmux.conf9
-rw-r--r--user-profiles/utils.nix23
-rw-r--r--user-profiles/yt-dlp.nix6
-rw-r--r--user-profiles/zsh/default.nix100
-rw-r--r--user-profiles/zsh/zshrc7
-rw-r--r--users/gkleen/default.nix34
-rw-r--r--users/gkleen/gitignore2
-rw-r--r--users/root.nix2
181 files changed, 7971 insertions, 3148 deletions
diff --git a/.gitignore b/.gitignore
index f30fe710..8d4a68d6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,5 @@
1**/result 1**/result
2**/result-* 2**/result-*
3**/#*#
4**/.#*
5**/.gup 3**/.gup
6.direnv 4.direnv
7 5
diff --git a/.sops.yaml b/.sops.yaml
index 948383b2..a65dca8e 100644
--- a/.sops.yaml
+++ b/.sops.yaml
@@ -8,6 +8,12 @@ creation_rules:
8 - path_regex: ^hosts/surtr/email/ca 8 - path_regex: ^hosts/surtr/email/ca
9 key_groups: 9 key_groups:
10 - age: [ *admin_gkleen ] 10 - age: [ *admin_gkleen ]
11 - path_regex: ^home-modules/lmu-hausschrift/
12 key_groups:
13 - age: [ *admin_gkleen ]
14 - path_regex: ^accounts/gkleen@sif/
15 key_groups:
16 - age: [ *admin_gkleen ]
11 - path_regex: surtr\/?[^\/]*$ 17 - path_regex: surtr\/?[^\/]*$
12 key_groups: 18 key_groups:
13 - age: [ *admin_gkleen, *machine_surtr ] 19 - age: [ *admin_gkleen, *machine_surtr ]
diff --git a/_sources/generated.json b/_sources/generated.json
index e82d7fe6..4498b987 100644
--- a/_sources/generated.json
+++ b/_sources/generated.json
@@ -22,7 +22,7 @@
22 }, 22 },
23 "bpf-examples": { 23 "bpf-examples": {
24 "cargoLocks": null, 24 "cargoLocks": null,
25 "date": "2025-01-03", 25 "date": "2025-07-04",
26 "extract": null, 26 "extract": null,
27 "name": "bpf-examples", 27 "name": "bpf-examples",
28 "passthru": null, 28 "passthru": null,
@@ -34,12 +34,12 @@
34 "name": null, 34 "name": null,
35 "owner": "xdp-project", 35 "owner": "xdp-project",
36 "repo": "bpf-examples", 36 "repo": "bpf-examples",
37 "rev": "8d53e6fc46ae625bd16b38eb1007ece99460eada", 37 "rev": "588d0064f575e58878f27bfa7eb52e460150dc6a",
38 "sha256": "sha256-BUncjyaywmtSMVhbWZDy9XiNlGJet8Z0lzmUqm3f+HU=", 38 "sha256": "sha256-8wp2lfp7RQYmMJmHp0hzpCYQXj6hQXDIIKhpiCBYTt0=",
39 "sparseCheckout": [], 39 "sparseCheckout": [],
40 "type": "github" 40 "type": "github"
41 }, 41 },
42 "version": "8d53e6fc46ae625bd16b38eb1007ece99460eada" 42 "version": "588d0064f575e58878f27bfa7eb52e460150dc6a"
43 }, 43 },
44 "emacs-scratch_el": { 44 "emacs-scratch_el": {
45 "cargoLocks": null, 45 "cargoLocks": null,
@@ -76,12 +76,12 @@
76 "name": null, 76 "name": null,
77 "owner": "Mange", 77 "owner": "Mange",
78 "repo": "emoji-data", 78 "repo": "emoji-data",
79 "rev": "v2.6", 79 "rev": "v2.7",
80 "sha256": "sha256-6nBiT9q139P1pXLqkV1JejE0s2rZn1gUbNsejXJR6RU=", 80 "sha256": "sha256-bUFh0Q7xcnKTBgVBUJU8BH6zzq1Y3krLfJJAgx5TqKs=",
81 "sparseCheckout": [], 81 "sparseCheckout": [],
82 "type": "github" 82 "type": "github"
83 }, 83 },
84 "version": "v2.6" 84 "version": "v2.7"
85 }, 85 },
86 "lesspipe": { 86 "lesspipe": {
87 "cargoLocks": null, 87 "cargoLocks": null,
@@ -91,15 +91,15 @@
91 "passthru": null, 91 "passthru": null,
92 "pinned": false, 92 "pinned": false,
93 "src": { 93 "src": {
94 "sha256": "sha256-afJuTByGUMU6kFqGGa3pbPaFVdYGcJYiR0RfDNYNgDk=", 94 "sha256": "sha256-GCtcIXGrMH6LOKxjnB2SkUSChQnMj5d939i2atvqK+Q=",
95 "type": "tarball", 95 "type": "tarball",
96 "url": "https://github.com/wofr06/lesspipe/archive/refs/tags/v2.17.tar.gz" 96 "url": "https://github.com/wofr06/lesspipe/archive/refs/tags/v2.18.tar.gz"
97 }, 97 },
98 "version": "2.17" 98 "version": "2.18"
99 }, 99 },
100 "mako": { 100 "mako": {
101 "cargoLocks": null, 101 "cargoLocks": null,
102 "date": "2025-01-19", 102 "date": "2025-04-17",
103 "extract": null, 103 "extract": null,
104 "name": "mako", 104 "name": "mako",
105 "passthru": null, 105 "passthru": null,
@@ -109,13 +109,13 @@
109 "fetchSubmodules": false, 109 "fetchSubmodules": false,
110 "leaveDotGit": false, 110 "leaveDotGit": false,
111 "name": null, 111 "name": null,
112 "rev": "57a258c1f8861200e0623153f1b79065d4ddabd8", 112 "rev": "84637d1cb42def0ef74d6ec961e05c3900b4c23f",
113 "sha256": "sha256-9PcZLpIfGR8SmZf5e2rDZhF+y3kfSaFw5DneDXHMGTc=", 113 "sha256": "sha256-5Mv0P/SoUiJIiYvGm5wvNZhtz2ytwsqqRc3Pk3ju0WA=",
114 "sparseCheckout": [], 114 "sparseCheckout": [],
115 "type": "git", 115 "type": "git",
116 "url": "https://github.com/emersion/mako" 116 "url": "https://github.com/emersion/mako"
117 }, 117 },
118 "version": "57a258c1f8861200e0623153f1b79065d4ddabd8" 118 "version": "84637d1cb42def0ef74d6ec961e05c3900b4c23f"
119 }, 119 },
120 "mpv-autosave": { 120 "mpv-autosave": {
121 "cargoLocks": null, 121 "cargoLocks": null,
@@ -202,7 +202,7 @@
202 }, 202 },
203 "mpv-reload": { 203 "mpv-reload": {
204 "cargoLocks": null, 204 "cargoLocks": null,
205 "date": "2024-03-22", 205 "date": "2025-02-07",
206 "extract": null, 206 "extract": null,
207 "name": "mpv-reload", 207 "name": "mpv-reload",
208 "passthru": null, 208 "passthru": null,
@@ -214,16 +214,16 @@
214 "name": null, 214 "name": null,
215 "owner": "4e6", 215 "owner": "4e6",
216 "repo": "mpv-reload", 216 "repo": "mpv-reload",
217 "rev": "1a6a9383ba1774708fddbd976e7a9b72c3eec938", 217 "rev": "60e6fb1c578aa9af80d725857dac8e439095b033",
218 "sha256": "sha256-BshxCjec/UNGyiC0/g1Rai2NvG2qOIHXDDEUYwwdij0=", 218 "sha256": "sha256-elA9bi5ov5MbehLD1kyS4Z8zKgTc+8dcOPq32muRGcE=",
219 "sparseCheckout": [], 219 "sparseCheckout": [],
220 "type": "github" 220 "type": "github"
221 }, 221 },
222 "version": "1a6a9383ba1774708fddbd976e7a9b72c3eec938" 222 "version": "60e6fb1c578aa9af80d725857dac8e439095b033"
223 }, 223 },
224 "mpv-subselect": { 224 "mpv-subselect": {
225 "cargoLocks": null, 225 "cargoLocks": null,
226 "date": "2024-12-22", 226 "date": "2025-04-04",
227 "extract": null, 227 "extract": null,
228 "name": "mpv-subselect", 228 "name": "mpv-subselect",
229 "passthru": null, 229 "passthru": null,
@@ -233,13 +233,13 @@
233 "fetchSubmodules": false, 233 "fetchSubmodules": false,
234 "leaveDotGit": false, 234 "leaveDotGit": false,
235 "name": null, 235 "name": null,
236 "rev": "77d0148aa6aa952f07f06212cabe32d54dfdf49e", 236 "rev": "26d24a0fd1d69988eaedda6056a2c87d0a55b6cb",
237 "sha256": "sha256-VxwwTxE8c8rRQt/m2NA7cRC7+7O1ItYFFGv81nxqIxg=", 237 "sha256": "sha256-+eVga4b7KIBnfrtmlgq/0HNjQVS3SK6YWVXCPvOeOOc=",
238 "sparseCheckout": [], 238 "sparseCheckout": [],
239 "type": "git", 239 "type": "git",
240 "url": "https://github.com/CogentRedTester/mpv-sub-select" 240 "url": "https://github.com/CogentRedTester/mpv-sub-select"
241 }, 241 },
242 "version": "77d0148aa6aa952f07f06212cabe32d54dfdf49e" 242 "version": "26d24a0fd1d69988eaedda6056a2c87d0a55b6cb"
243 }, 243 },
244 "mpv-youtube-quality": { 244 "mpv-youtube-quality": {
245 "cargoLocks": null, 245 "cargoLocks": null,
@@ -261,6 +261,36 @@
261 }, 261 },
262 "version": "1f8c31457459ffc28cd1c3f3c2235a53efad7148" 262 "version": "1f8c31457459ffc28cd1c3f3c2235a53efad7148"
263 }, 263 },
264 "netbootxyz-efi": {
265 "cargoLocks": null,
266 "date": null,
267 "extract": null,
268 "name": "netbootxyz-efi",
269 "passthru": null,
270 "pinned": false,
271 "src": {
272 "name": null,
273 "sha256": "sha256-8kd17ChqLuVH5/OdPc2rVDKEDWHl9ZWLh8k+EBrCGH8=",
274 "type": "url",
275 "url": "https://github.com/netbootxyz/netboot.xyz/releases/download/2.0.87/netboot.xyz.efi"
276 },
277 "version": "2.0.87"
278 },
279 "netbootxyz-lkrn": {
280 "cargoLocks": null,
281 "date": null,
282 "extract": null,
283 "name": "netbootxyz-lkrn",
284 "passthru": null,
285 "pinned": false,
286 "src": {
287 "name": null,
288 "sha256": "sha256-/qY3NdRC0SghQ4kamrkm9EFumrKlirqDCJ+XY+jHWLA=",
289 "type": "url",
290 "url": "https://github.com/netbootxyz/netboot.xyz/releases/download/2.0.87/netboot.xyz.lkrn"
291 },
292 "version": "2.0.87"
293 },
264 "postfix-mta-sts-resolver": { 294 "postfix-mta-sts-resolver": {
265 "cargoLocks": null, 295 "cargoLocks": null,
266 "date": null, 296 "date": null,
@@ -269,11 +299,11 @@
269 "passthru": null, 299 "passthru": null,
270 "pinned": false, 300 "pinned": false,
271 "src": { 301 "src": {
272 "sha256": "sha256-wpBbT/KXd2iU6Jn6Y/6i5C+Wv3OsUTjFcJDhUm6w46I=", 302 "sha256": "sha256-DrPWxAlzdtb5K0Z+yVi+rL1h7CyLj0/Fiio8B2H/Ssg=",
273 "type": "tarball", 303 "type": "tarball",
274 "url": "https://github.com/Snawoot/postfix-mta-sts-resolver/archive/refs/tags/v1.4.0.tar.gz" 304 "url": "https://github.com/Snawoot/postfix-mta-sts-resolver/archive/refs/tags/v1.5.0.tar.gz"
275 }, 305 },
276 "version": "1.4.0" 306 "version": "1.5.0"
277 }, 307 },
278 "postfwd": { 308 "postfwd": {
279 "cargoLocks": null, 309 "cargoLocks": null,
@@ -297,11 +327,11 @@
297 "passthru": null, 327 "passthru": null,
298 "pinned": false, 328 "pinned": false,
299 "src": { 329 "src": {
300 "sha256": "sha256-Nz/lBhQbzWSnOKN4n0OUdJzDTpf3mfY0+FXoCqF03TU=", 330 "sha256": "sha256-JDKt+MzxxyaFWnzuq/7FfT/JPUknH/RRw4Cb8XDOtlk=",
301 "type": "tarball", 331 "type": "tarball",
302 "url": "https://github.com/hansmi/prometheus-lvm-exporter/archive/refs/tags/v0.4.0.tar.gz" 332 "url": "https://github.com/hansmi/prometheus-lvm-exporter/archive/refs/tags/v0.6.0.tar.gz"
303 }, 333 },
304 "version": "0.4.0" 334 "version": "0.6.0"
305 }, 335 },
306 "psql-versioning": { 336 "psql-versioning": {
307 "cargoLocks": null, 337 "cargoLocks": null,
@@ -365,6 +395,26 @@
365 }, 395 },
366 "version": "0.2.1" 396 "version": "0.2.1"
367 }, 397 },
398 "swayosd": {
399 "cargoLocks": null,
400 "date": "2025-07-07",
401 "extract": null,
402 "name": "swayosd",
403 "passthru": null,
404 "pinned": false,
405 "src": {
406 "deepClone": false,
407 "fetchSubmodules": false,
408 "leaveDotGit": false,
409 "name": null,
410 "rev": "73aed75146b81aaf67c4301353790ff5a17aed1f",
411 "sha256": "sha256-p31HNelptAw7Sk0NmYP4FkoUCdA5uAsrXC20JJp24Vw=",
412 "sparseCheckout": [],
413 "type": "git",
414 "url": "https://github.com/ErikReider/SwayOSD"
415 },
416 "version": "73aed75146b81aaf67c4301353790ff5a17aed1f"
417 },
368 "tomorrow-night-paradise-theme": { 418 "tomorrow-night-paradise-theme": {
369 "cargoLocks": null, 419 "cargoLocks": null,
370 "date": "2012-06-04", 420 "date": "2012-06-04",
@@ -387,7 +437,7 @@
387 }, 437 },
388 "v4l2loopback": { 438 "v4l2loopback": {
389 "cargoLocks": null, 439 "cargoLocks": null,
390 "date": "2025-01-18", 440 "date": "2025-07-08",
391 "extract": null, 441 "extract": null,
392 "name": "v4l2loopback", 442 "name": "v4l2loopback",
393 "passthru": null, 443 "passthru": null,
@@ -399,16 +449,16 @@
399 "name": null, 449 "name": null,
400 "owner": "umlaeute", 450 "owner": "umlaeute",
401 "repo": "v4l2loopback", 451 "repo": "v4l2loopback",
402 "rev": "39ad8a43522c18b5e4f4363ce053f604312fc413", 452 "rev": "e3fb6825ebb4bc6da2e15a515e24769d76c93332",
403 "sha256": "sha256-A1p5ZfoMlw6/J3vBdQcXMvERdyBnqs9Ca+0LcLnu7b8=", 453 "sha256": "sha256-cgxdWuIwmNaOIYmMA1mEvC+1FQFhTGsffVv71md5yXo=",
404 "sparseCheckout": [], 454 "sparseCheckout": [],
405 "type": "github" 455 "type": "github"
406 }, 456 },
407 "version": "39ad8a43522c18b5e4f4363ce053f604312fc413" 457 "version": "e3fb6825ebb4bc6da2e15a515e24769d76c93332"
408 }, 458 },
409 "xcompose": { 459 "xcompose": {
410 "cargoLocks": null, 460 "cargoLocks": null,
411 "date": "2022-09-14", 461 "date": "2025-06-05",
412 "extract": null, 462 "extract": null,
413 "name": "xcompose", 463 "name": "xcompose",
414 "passthru": null, 464 "passthru": null,
@@ -420,12 +470,12 @@
420 "name": null, 470 "name": null,
421 "owner": "kragen", 471 "owner": "kragen",
422 "repo": "xcompose", 472 "repo": "xcompose",
423 "rev": "cd8d3e622f547ec9f83d7f64f51d4a27ee812681", 473 "rev": "4d8eab4d05a19537ce79294ae0459fdae78ffb20",
424 "sha256": "sha256-fkl2lDv/DdrqPjVsEUKSRD3BNGwTjTsA0ovI8akFI6U=", 474 "sha256": "sha256-vKY4u5Z2IL111orLLkkF4AoVzqluKG/VQhNUUCqO/k8=",
425 "sparseCheckout": [], 475 "sparseCheckout": [],
426 "type": "github" 476 "type": "github"
427 }, 477 },
428 "version": "cd8d3e622f547ec9f83d7f64f51d4a27ee812681" 478 "version": "4d8eab4d05a19537ce79294ae0459fdae78ffb20"
429 }, 479 },
430 "yt-dlp": { 480 "yt-dlp": {
431 "cargoLocks": null, 481 "cargoLocks": null,
@@ -436,10 +486,10 @@
436 "pinned": false, 486 "pinned": false,
437 "src": { 487 "src": {
438 "name": null, 488 "name": null,
439 "sha256": "sha256-HJc4JmkhrUPFaK0BrDNi+3x69Uknb77JK9cvFA2hYkA=", 489 "sha256": "sha256-bQroVcClW/zCjf+6gE7IUlublV00pBGRoVYaTOwD2L0=",
440 "type": "url", 490 "type": "url",
441 "url": "https://pypi.org/packages/source/y/yt_dlp/yt_dlp-2025.1.26.tar.gz" 491 "url": "https://pypi.org/packages/source/y/yt_dlp/yt_dlp-2025.6.30.tar.gz"
442 }, 492 },
443 "version": "2025.1.26" 493 "version": "2025.6.30"
444 } 494 }
445} \ No newline at end of file 495} \ No newline at end of file
diff --git a/_sources/generated.nix b/_sources/generated.nix
index c1a0c6a0..f470ad9e 100644
--- a/_sources/generated.nix
+++ b/_sources/generated.nix
@@ -18,15 +18,15 @@
18 }; 18 };
19 bpf-examples = { 19 bpf-examples = {
20 pname = "bpf-examples"; 20 pname = "bpf-examples";
21 version = "8d53e6fc46ae625bd16b38eb1007ece99460eada"; 21 version = "588d0064f575e58878f27bfa7eb52e460150dc6a";
22 src = fetchFromGitHub { 22 src = fetchFromGitHub {
23 owner = "xdp-project"; 23 owner = "xdp-project";
24 repo = "bpf-examples"; 24 repo = "bpf-examples";
25 rev = "8d53e6fc46ae625bd16b38eb1007ece99460eada"; 25 rev = "588d0064f575e58878f27bfa7eb52e460150dc6a";
26 fetchSubmodules = true; 26 fetchSubmodules = true;
27 sha256 = "sha256-BUncjyaywmtSMVhbWZDy9XiNlGJet8Z0lzmUqm3f+HU="; 27 sha256 = "sha256-8wp2lfp7RQYmMJmHp0hzpCYQXj6hQXDIIKhpiCBYTt0=";
28 }; 28 };
29 date = "2025-01-03"; 29 date = "2025-07-04";
30 }; 30 };
31 emacs-scratch_el = { 31 emacs-scratch_el = {
32 pname = "emacs-scratch_el"; 32 pname = "emacs-scratch_el";
@@ -42,36 +42,36 @@
42 }; 42 };
43 emoji-data = { 43 emoji-data = {
44 pname = "emoji-data"; 44 pname = "emoji-data";
45 version = "v2.6"; 45 version = "v2.7";
46 src = fetchFromGitHub { 46 src = fetchFromGitHub {
47 owner = "Mange"; 47 owner = "Mange";
48 repo = "emoji-data"; 48 repo = "emoji-data";
49 rev = "v2.6"; 49 rev = "v2.7";
50 fetchSubmodules = true; 50 fetchSubmodules = true;
51 sha256 = "sha256-6nBiT9q139P1pXLqkV1JejE0s2rZn1gUbNsejXJR6RU="; 51 sha256 = "sha256-bUFh0Q7xcnKTBgVBUJU8BH6zzq1Y3krLfJJAgx5TqKs=";
52 }; 52 };
53 }; 53 };
54 lesspipe = { 54 lesspipe = {
55 pname = "lesspipe"; 55 pname = "lesspipe";
56 version = "2.17"; 56 version = "2.18";
57 src = fetchTarball { 57 src = fetchTarball {
58 url = "https://github.com/wofr06/lesspipe/archive/refs/tags/v2.17.tar.gz"; 58 url = "https://github.com/wofr06/lesspipe/archive/refs/tags/v2.18.tar.gz";
59 sha256 = "sha256-afJuTByGUMU6kFqGGa3pbPaFVdYGcJYiR0RfDNYNgDk="; 59 sha256 = "sha256-GCtcIXGrMH6LOKxjnB2SkUSChQnMj5d939i2atvqK+Q=";
60 }; 60 };
61 }; 61 };
62 mako = { 62 mako = {
63 pname = "mako"; 63 pname = "mako";
64 version = "57a258c1f8861200e0623153f1b79065d4ddabd8"; 64 version = "84637d1cb42def0ef74d6ec961e05c3900b4c23f";
65 src = fetchgit { 65 src = fetchgit {
66 url = "https://github.com/emersion/mako"; 66 url = "https://github.com/emersion/mako";
67 rev = "57a258c1f8861200e0623153f1b79065d4ddabd8"; 67 rev = "84637d1cb42def0ef74d6ec961e05c3900b4c23f";
68 fetchSubmodules = false; 68 fetchSubmodules = false;
69 deepClone = false; 69 deepClone = false;
70 leaveDotGit = false; 70 leaveDotGit = false;
71 sparseCheckout = [ ]; 71 sparseCheckout = [ ];
72 sha256 = "sha256-9PcZLpIfGR8SmZf5e2rDZhF+y3kfSaFw5DneDXHMGTc="; 72 sha256 = "sha256-5Mv0P/SoUiJIiYvGm5wvNZhtz2ytwsqqRc3Pk3ju0WA=";
73 }; 73 };
74 date = "2025-01-19"; 74 date = "2025-04-17";
75 }; 75 };
76 mpv-autosave = { 76 mpv-autosave = {
77 pname = "mpv-autosave"; 77 pname = "mpv-autosave";
@@ -124,29 +124,29 @@
124 }; 124 };
125 mpv-reload = { 125 mpv-reload = {
126 pname = "mpv-reload"; 126 pname = "mpv-reload";
127 version = "1a6a9383ba1774708fddbd976e7a9b72c3eec938"; 127 version = "60e6fb1c578aa9af80d725857dac8e439095b033";
128 src = fetchFromGitHub { 128 src = fetchFromGitHub {
129 owner = "4e6"; 129 owner = "4e6";
130 repo = "mpv-reload"; 130 repo = "mpv-reload";
131 rev = "1a6a9383ba1774708fddbd976e7a9b72c3eec938"; 131 rev = "60e6fb1c578aa9af80d725857dac8e439095b033";
132 fetchSubmodules = false; 132 fetchSubmodules = false;
133 sha256 = "sha256-BshxCjec/UNGyiC0/g1Rai2NvG2qOIHXDDEUYwwdij0="; 133 sha256 = "sha256-elA9bi5ov5MbehLD1kyS4Z8zKgTc+8dcOPq32muRGcE=";
134 }; 134 };
135 date = "2024-03-22"; 135 date = "2025-02-07";
136 }; 136 };
137 mpv-subselect = { 137 mpv-subselect = {
138 pname = "mpv-subselect"; 138 pname = "mpv-subselect";
139 version = "77d0148aa6aa952f07f06212cabe32d54dfdf49e"; 139 version = "26d24a0fd1d69988eaedda6056a2c87d0a55b6cb";
140 src = fetchgit { 140 src = fetchgit {
141 url = "https://github.com/CogentRedTester/mpv-sub-select"; 141 url = "https://github.com/CogentRedTester/mpv-sub-select";
142 rev = "77d0148aa6aa952f07f06212cabe32d54dfdf49e"; 142 rev = "26d24a0fd1d69988eaedda6056a2c87d0a55b6cb";
143 fetchSubmodules = false; 143 fetchSubmodules = false;
144 deepClone = false; 144 deepClone = false;
145 leaveDotGit = false; 145 leaveDotGit = false;
146 sparseCheckout = [ ]; 146 sparseCheckout = [ ];
147 sha256 = "sha256-VxwwTxE8c8rRQt/m2NA7cRC7+7O1ItYFFGv81nxqIxg="; 147 sha256 = "sha256-+eVga4b7KIBnfrtmlgq/0HNjQVS3SK6YWVXCPvOeOOc=";
148 }; 148 };
149 date = "2024-12-22"; 149 date = "2025-04-04";
150 }; 150 };
151 mpv-youtube-quality = { 151 mpv-youtube-quality = {
152 pname = "mpv-youtube-quality"; 152 pname = "mpv-youtube-quality";
@@ -162,12 +162,28 @@
162 }; 162 };
163 date = "2020-02-10"; 163 date = "2020-02-10";
164 }; 164 };
165 netbootxyz-efi = {
166 pname = "netbootxyz-efi";
167 version = "2.0.87";
168 src = fetchurl {
169 url = "https://github.com/netbootxyz/netboot.xyz/releases/download/2.0.87/netboot.xyz.efi";
170 sha256 = "sha256-8kd17ChqLuVH5/OdPc2rVDKEDWHl9ZWLh8k+EBrCGH8=";
171 };
172 };
173 netbootxyz-lkrn = {
174 pname = "netbootxyz-lkrn";
175 version = "2.0.87";
176 src = fetchurl {
177 url = "https://github.com/netbootxyz/netboot.xyz/releases/download/2.0.87/netboot.xyz.lkrn";
178 sha256 = "sha256-/qY3NdRC0SghQ4kamrkm9EFumrKlirqDCJ+XY+jHWLA=";
179 };
180 };
165 postfix-mta-sts-resolver = { 181 postfix-mta-sts-resolver = {
166 pname = "postfix-mta-sts-resolver"; 182 pname = "postfix-mta-sts-resolver";
167 version = "1.4.0"; 183 version = "1.5.0";
168 src = fetchTarball { 184 src = fetchTarball {
169 url = "https://github.com/Snawoot/postfix-mta-sts-resolver/archive/refs/tags/v1.4.0.tar.gz"; 185 url = "https://github.com/Snawoot/postfix-mta-sts-resolver/archive/refs/tags/v1.5.0.tar.gz";
170 sha256 = "sha256-wpBbT/KXd2iU6Jn6Y/6i5C+Wv3OsUTjFcJDhUm6w46I="; 186 sha256 = "sha256-DrPWxAlzdtb5K0Z+yVi+rL1h7CyLj0/Fiio8B2H/Ssg=";
171 }; 187 };
172 }; 188 };
173 postfwd = { 189 postfwd = {
@@ -180,10 +196,10 @@
180 }; 196 };
181 prometheus-lvm-exporter = { 197 prometheus-lvm-exporter = {
182 pname = "prometheus-lvm-exporter"; 198 pname = "prometheus-lvm-exporter";
183 version = "0.4.0"; 199 version = "0.6.0";
184 src = fetchTarball { 200 src = fetchTarball {
185 url = "https://github.com/hansmi/prometheus-lvm-exporter/archive/refs/tags/v0.4.0.tar.gz"; 201 url = "https://github.com/hansmi/prometheus-lvm-exporter/archive/refs/tags/v0.6.0.tar.gz";
186 sha256 = "sha256-Nz/lBhQbzWSnOKN4n0OUdJzDTpf3mfY0+FXoCqF03TU="; 202 sha256 = "sha256-JDKt+MzxxyaFWnzuq/7FfT/JPUknH/RRw4Cb8XDOtlk=";
187 }; 203 };
188 }; 204 };
189 psql-versioning = { 205 psql-versioning = {
@@ -224,6 +240,20 @@
224 sha256 = "sha256-7d/0fepOvdswuBGJCCMULB2kXOFBLP78yqX4NmByCF8="; 240 sha256 = "sha256-7d/0fepOvdswuBGJCCMULB2kXOFBLP78yqX4NmByCF8=";
225 }; 241 };
226 }; 242 };
243 swayosd = {
244 pname = "swayosd";
245 version = "73aed75146b81aaf67c4301353790ff5a17aed1f";
246 src = fetchgit {
247 url = "https://github.com/ErikReider/SwayOSD";
248 rev = "73aed75146b81aaf67c4301353790ff5a17aed1f";
249 fetchSubmodules = false;
250 deepClone = false;
251 leaveDotGit = false;
252 sparseCheckout = [ ];
253 sha256 = "sha256-p31HNelptAw7Sk0NmYP4FkoUCdA5uAsrXC20JJp24Vw=";
254 };
255 date = "2025-07-07";
256 };
227 tomorrow-night-paradise-theme = { 257 tomorrow-night-paradise-theme = {
228 pname = "tomorrow-night-paradise-theme"; 258 pname = "tomorrow-night-paradise-theme";
229 version = "70225a5bf90d495e13a9260bfdc268632ece0801"; 259 version = "70225a5bf90d495e13a9260bfdc268632ece0801";
@@ -240,34 +270,34 @@
240 }; 270 };
241 v4l2loopback = { 271 v4l2loopback = {
242 pname = "v4l2loopback"; 272 pname = "v4l2loopback";
243 version = "39ad8a43522c18b5e4f4363ce053f604312fc413"; 273 version = "e3fb6825ebb4bc6da2e15a515e24769d76c93332";
244 src = fetchFromGitHub { 274 src = fetchFromGitHub {
245 owner = "umlaeute"; 275 owner = "umlaeute";
246 repo = "v4l2loopback"; 276 repo = "v4l2loopback";
247 rev = "39ad8a43522c18b5e4f4363ce053f604312fc413"; 277 rev = "e3fb6825ebb4bc6da2e15a515e24769d76c93332";
248 fetchSubmodules = true; 278 fetchSubmodules = true;
249 sha256 = "sha256-A1p5ZfoMlw6/J3vBdQcXMvERdyBnqs9Ca+0LcLnu7b8="; 279 sha256 = "sha256-cgxdWuIwmNaOIYmMA1mEvC+1FQFhTGsffVv71md5yXo=";
250 }; 280 };
251 date = "2025-01-18"; 281 date = "2025-07-08";
252 }; 282 };
253 xcompose = { 283 xcompose = {
254 pname = "xcompose"; 284 pname = "xcompose";
255 version = "cd8d3e622f547ec9f83d7f64f51d4a27ee812681"; 285 version = "4d8eab4d05a19537ce79294ae0459fdae78ffb20";
256 src = fetchFromGitHub { 286 src = fetchFromGitHub {
257 owner = "kragen"; 287 owner = "kragen";
258 repo = "xcompose"; 288 repo = "xcompose";
259 rev = "cd8d3e622f547ec9f83d7f64f51d4a27ee812681"; 289 rev = "4d8eab4d05a19537ce79294ae0459fdae78ffb20";
260 fetchSubmodules = false; 290 fetchSubmodules = false;
261 sha256 = "sha256-fkl2lDv/DdrqPjVsEUKSRD3BNGwTjTsA0ovI8akFI6U="; 291 sha256 = "sha256-vKY4u5Z2IL111orLLkkF4AoVzqluKG/VQhNUUCqO/k8=";
262 }; 292 };
263 date = "2022-09-14"; 293 date = "2025-06-05";
264 }; 294 };
265 yt-dlp = { 295 yt-dlp = {
266 pname = "yt-dlp"; 296 pname = "yt-dlp";
267 version = "2025.1.26"; 297 version = "2025.6.30";
268 src = fetchurl { 298 src = fetchurl {
269 url = "https://pypi.org/packages/source/y/yt_dlp/yt_dlp-2025.1.26.tar.gz"; 299 url = "https://pypi.org/packages/source/y/yt_dlp/yt_dlp-2025.6.30.tar.gz";
270 sha256 = "sha256-HJc4JmkhrUPFaK0BrDNi+3x69Uknb77JK9cvFA2hYkA="; 300 sha256 = "sha256-bQroVcClW/zCjf+6gE7IUlublV00pBGRoVYaTOwD2L0=";
271 }; 301 };
272 }; 302 };
273} 303}
diff --git a/accounts/gkleen@eostre.nix b/accounts/gkleen@eostre.nix
index 72818d44..28daf3fd 100644
--- a/accounts/gkleen@eostre.nix
+++ b/accounts/gkleen@eostre.nix
@@ -1,16 +1,16 @@
1{ flake, userName, pkgs, ... }: 1{ flake, userName, pkgs, ... }:
2{ 2{
3 imports = with flake.nixosModules.userProfiles.${userName}; [ 3 imports = with flake.nixosModules.userProfiles.${userName}; [
4 zsh utils tmux 4 utils
5 ]; 5 ];
6 6
7 config = { 7 config = {
8 home-manager.users.${userName} = { 8 home-manager.users.${userName} = {
9 home.stateVersion = "20.09"; 9 home.stateVersion = "20.09";
10 10
11 nixpkgs.config = { 11 # nixpkgs.config = {
12 allowUnfree = true; 12 # allowUnfree = true;
13 }; 13 # };
14 14
15 home.packages = with pkgs; [ 15 home.packages = with pkgs; [
16 thunderbird libreoffice element-desktop keepassxc vlc 16 thunderbird libreoffice element-desktop keepassxc vlc
diff --git a/accounts/gkleen@installer.nix b/accounts/gkleen@installer.nix
index c7a418f8..5fe1db38 100644
--- a/accounts/gkleen@installer.nix
+++ b/accounts/gkleen@installer.nix
@@ -1,7 +1,11 @@
1{ userName, ... }: 1{ flake, userName, ... }:
2 2
3{ 3{
4 home-manager.users.${userName} = { config, ... } : { 4 imports = with flake.nixosModules.userProfiles.${userName}; [
5 zsh tmux
6 ];
7
8 config.home-manager.users.${userName} = { config, ... } : {
5 home.stateVersion = config.home.version.release; 9 home.stateVersion = config.home.version.release;
6 }; 10 };
7} 11}
diff --git a/accounts/gkleen@sif/default.nix b/accounts/gkleen@sif/default.nix
index b47e25a4..a1a694ea 100644
--- a/accounts/gkleen@sif/default.nix
+++ b/accounts/gkleen@sif/default.nix
@@ -4,33 +4,6 @@ with lib;
4 4
5let 5let
6 cfg = config.home-manager.users.${userName}; 6 cfg = config.home-manager.users.${userName};
7 emacsScratch = pkgs.stdenv.mkDerivation (sources.emacs-scratch_el // rec {
8 phases = [ "installPhase" ];
9
10 installPhase = ''
11 mkdir -p $out/share/emacs/site-lisp
12 cp $src/scratch.el $out/share/emacs/site-lisp/default.el
13 '';
14 });
15 muteScript = pkgs.stdenv.mkDerivation {
16 name = "mute";
17 src = ./scripts/mute.zsh;
18
19 buildInputs = with pkgs; [ makeWrapper ];
20
21 phases = [ "installPhase" ];
22
23 installPhase = ''
24 mkdir -p $out/bin
25 install -m 0755 $src $out/bin/mute
26 wrapProgram $out/bin/mute \
27 --prefix PATH : ${pkgs.zsh}/bin \
28 --prefix PATH : ${pkgs.findutils}/bin \
29 --prefix PATH : ${pkgs.util-linux}/bin \
30 --prefix PATH : ${pkgs.coreutils}/bin \
31 --prefix PATH : ${pkgs.pulseaudio}/bin
32 '';
33 };
34 wrapElectron = { package, bin ? package.meta.mainProgram or package.pname or (pkgs.lib.strings.nameFromURL package.name "-"), outBin ? bin, sandbox ? true }: pkgs.symlinkJoin { 7 wrapElectron = { package, bin ? package.meta.mainProgram or package.pname or (pkgs.lib.strings.nameFromURL package.name "-"), outBin ? bin, sandbox ? true }: pkgs.symlinkJoin {
35 name = "${package.name}-wrapped"; 8 name = "${package.name}-wrapped";
36 buildInputs = with pkgs; [ makeWrapper ]; 9 buildInputs = with pkgs; [ makeWrapper ];
@@ -47,10 +20,6 @@ let
47 ''; 20 '';
48 }; 21 };
49 22
50 wrappedChrome = wrapElectron { package = pkgs.google-chrome; outBin = "google-chrome"; };
51 wrappedZulip = wrapElectron { package = pkgs.zulip; bin = "zulip"; outBin = "zulip"; };
52 wrappedElementDesktop = wrapElectron { package = pkgs.element-desktop; bin = "element-desktop"; };
53 wrappedRocketChatDesktop = wrapElectron { package = pkgs.rocketchat-desktop; bin = "rocketchat-desktop"; outBin = "rocketchat"; };
54 wrappedYTMDesktop = wrapElectron { package = pkgs.ytmdesktop; sandbox = false; }; 23 wrappedYTMDesktop = wrapElectron { package = pkgs.ytmdesktop; sandbox = false; };
55 24
56 wrappedKeepassxc = pkgs.symlinkJoin { 25 wrappedKeepassxc = pkgs.symlinkJoin {
@@ -63,7 +32,7 @@ let
63 text = '' 32 text = ''
64 [D-BUS Service] 33 [D-BUS Service]
65 Name=org.keepassxc.KeePassXC.MainWindow 34 Name=org.keepassxc.KeePassXC.MainWindow
66 Exec=${pkgs.coreutils}/bin/false 35 Exec=${lib.getExe' pkgs.coreutils "false"}
67 SystemdService=keepassxc.service 36 SystemdService=keepassxc.service
68 ''; 37 '';
69 }) 38 })
@@ -73,36 +42,46 @@ let
73 text = '' 42 text = ''
74 [D-BUS Service] 43 [D-BUS Service]
75 Name=org.freedesktop.secrets 44 Name=org.freedesktop.secrets
76 Exec=${pkgs.coreutils}/bin/false 45 Exec=${lib.getExe' pkgs.coreutils "false"}
77 SystemdService=keepassxc.service 46 SystemdService=keepassxc.service
78 ''; 47 '';
79 }) 48 })
80 ]; 49 ];
81 }; 50 };
82 51
83 lockCommand = "${config.systemd.package}/bin/systemctl --user start gtklock.service"; 52 lockCommand = "${lib.getExe' config.systemd.package "systemctl"} --user start gtklock.service";
53
54 editor = pkgs.symlinkJoin {
55 inherit (cfg.services.emacs.package) name;
56 buildInputs = with pkgs; [ makeWrapper ];
57 paths = [ cfg.services.emacs.package ];
58 postBuild = ''
59 wrapProgram $out/bin/emacsclient \
60 --inherit-argv0 \
61 --add-flags ${lib.escapeShellArg (lib.escapeShellArgs cfg.services.emacs.client.arguments)}
62 '';
63 };
84in { 64in {
85 imports = with flake.nixosModules.userProfiles.${userName}; [ 65 imports = with flake.nixosModules.userProfiles.${userName}; [
86 mpv yt-dlp (args: import ./xcompose.nix (inputs // args)) 66 zsh tmux mpv yt-dlp (args: import ./xcompose.nix (inputs // args))
87 ]; 67 ];
88 68
89 config = { 69 config = {
90 services.displayManager.defaultSession = "Hyprland"; # "none+xmonad";
91
92 home-manager.users.${userName} = { 70 home-manager.users.${userName} = {
93 imports = [ 71 imports = [
94 ./libvirt 72 ./libvirt
95 ./niri 73 ./niri
74 ./synadm
96 flakeInputs.nix-index-database.hmModules.nix-index 75 flakeInputs.nix-index-database.hmModules.nix-index
97 flakeInputs.impermanence.nixosModules.home-manager.impermanence 76 flakeInputs.impermanence.nixosModules.home-manager.impermanence
98 ]; 77 ];
99 78
100 home.stateVersion = "20.09"; 79 home.stateVersion = "20.09";
101 80
102 nixpkgs.config = { 81 # nixpkgs.config = {
103 allowUnfree = true; 82 # allowUnfree = true;
104 zathura.useMupdf = false; 83 # zathura.useMupdf = false;
105 }; 84 # };
106 85
107 nix.registry = { 86 nix.registry = {
108 "flk" = { 87 "flk" = {
@@ -112,14 +91,14 @@ in {
112 }; 91 };
113 to = { 92 to = {
114 type = "git"; 93 type = "git";
115 url = "file:///home/gkleen/config/nixos-flakes"; 94 url = "file:///home/gkleen/projects/machines";
116 }; 95 };
117 }; 96 };
118 }; 97 };
119 98
120 programs = { 99 programs = {
121 ssh = { 100 ssh = {
122 matchBlocks = import ./ssh-hosts.nix { inherit pkgs; }; # customUtils.nixImport { dir = ./ssh-hosts; }; 101 matchBlocks = import ./ssh-hosts.nix inputs; # customUtils.nixImport { dir = ./ssh-hosts; };
123 extraConfig = '' 102 extraConfig = ''
124 Match host uniworx3.ifi.lmu.de,uniworx4.ifi.lmu.de,uniworx5.ifi.lmu.de,uni2workgw.ifi.lmu.de,blackbeard.tcs.ifi.lmu.de,gitlab2.rz.ifi.lmu.de,oregon.tcs.ifi.lmu.de !exec "nc -z -w 1 %h %p &>/dev/null" 103 Match host uniworx3.ifi.lmu.de,uniworx4.ifi.lmu.de,uniworx5.ifi.lmu.de,uni2workgw.ifi.lmu.de,blackbeard.tcs.ifi.lmu.de,gitlab2.rz.ifi.lmu.de,oregon.tcs.ifi.lmu.de !exec "nc -z -w 1 %h %p &>/dev/null"
125 ProxyJump remote.cip.ifi.lmu.de 104 ProxyJump remote.cip.ifi.lmu.de
@@ -137,8 +116,8 @@ in {
137 ''} 116 ''}
138 117
139 Match host *.mathinst.loc,*.math.lmu.de !host ssh.math.lmu.de !exec "nc -z -w 1 %h %p &>/dev/null" 118 Match host *.mathinst.loc,*.math.lmu.de !host ssh.math.lmu.de !exec "nc -z -w 1 %h %p &>/dev/null"
140 # ProxyCommand ${pkgs.socat}/bin/socat - SOCKS4A:127.0.0.1:%h:%p,socksport=8118 119 ProxyCommand ${lib.getExe pkgs.socat} - SOCKS4A:127.0.0.1:%h:%p,socksport=8118
141 ProxyJump ssh.math.lmu.de 120 # ProxyJump ssh.math.lmu.de
142 121
143 Match host *.cipmath.loc !host cip04.cipmath.loc,mgmt-cls01.cipmath.loc !exec "nc -z -w 1 %h %p &>/dev/null" 122 Match host *.cipmath.loc !host cip04.cipmath.loc,mgmt-cls01.cipmath.loc !exec "nc -z -w 1 %h %p &>/dev/null"
144 ProxyJump cip04 123 ProxyJump cip04
@@ -155,22 +134,31 @@ in {
155 134
156 emacs = { 135 emacs = {
157 enable = true; 136 enable = true;
158 package = pkgs.emacs29-pgtk; 137 package = pkgs.emacs-pgtk;
159 extraPackages = epkgs: with epkgs; [ 138 extraPackages = epkgs: with epkgs; [
160 evil evil-dvorak undo-tree magit haskell-tng-mode nix-mode 139 evil evil-dvorak undo-tree magit haskell-tng-mode nix-mode
161 yaml-mode json-mode shakespeare-mode smart-mode-line 140 yaml-mode json-mode shakespeare-mode smart-mode-line
162 highlight-parentheses highlight-symbol ag sass-mode lua-mode 141 highlight-parentheses highlight-symbol ag sass-mode
163 fira-code-mode use-package wanderlust # notmuch 142 lua-mode fira-code-mode use-package wanderlust # notmuch
164 git-gutter emacsScratch 143 git-gutter scratch edit-server mediawiki editorconfig
165 edit-server mediawiki editorconfig typescript-mode 144 typescript-mode markdown-mode nftables-mode rustic
166 markdown-mode nftables-mode rustic lsp-mode lsp-ui 145 lsp-mode lsp-ui direnv company projectile
167 direnv company projectile tomorrow-night-paradise-theme 146 tomorrow-night-paradise-theme
168 treesit-grammars.with-all-grammars magit-delta scad-mode 147 treesit-grammars.with-all-grammars magit-delta scad-mode
169 ]; 148 ];
170 overrides = self: super: { 149 overrides = self: super: {
171 tomorrow-night-paradise-theme = super.trivialBuild { 150 tomorrow-night-paradise-theme = super.trivialBuild {
172 inherit (sources.tomorrow-night-paradise-theme) pname version src; 151 inherit (sources.tomorrow-night-paradise-theme) pname version src;
173 }; 152 };
153 scratch = pkgs.stdenv.mkDerivation {
154 inherit (sources.emacs-scratch_el) pname version src;
155
156 phases = [ "unpackPhase" "installPhase" ];
157
158 installPhase = ''
159 install -Dt $out/share/emacs/site-lisp scratch.el
160 '';
161 };
174 }; 162 };
175 }; 163 };
176 firefox = { 164 firefox = {
@@ -184,6 +172,7 @@ in {
184 }; 172 };
185 }; 173 };
186 }; 174 };
175 chromium.enable = true;
187 176
188 zathura = { 177 zathura = {
189 enable = true; 178 enable = true;
@@ -199,13 +188,91 @@ in {
199 gpu-api = "vulkan"; 188 gpu-api = "vulkan";
200 }; 189 };
201 190
202 zsh.initExtra = '' 191 zsh.initContent = let
203 source ${./zshrc} 192 zshrc = pkgs.resholve.mkDerivation {
193 pname = "zshrc";
194 version = "0.0.0";
195
196 src = ./zshrc;
197
198 dontUnpack = true;
199 dontConfigure = true;
200 dontBuild = true;
201
202 installPhase = ''
203 mkdir -p $out/share
204 install "$src" $out/share/zshrc
205 '';
206
207 solutions = {
208 default = {
209 scripts = [ "share/zshrc" ];
210 interpreter = "none";
211 inputs = with pkgs; [
212 coreutils
213 rpm
214 binutils
215 squashfsTools
216 unzip
217 cfg.programs.git.package
218 magickWrapped
219 curl
220 file
221 gnutar
222 cpio
223 magic-wormhole
224 cfg.programs.zsh.package
225 fuse
226 util-linux
227 findutils
228 qrencode
229 tty-clock
230 cfg.programs.jq.package
231 eza
232 less
233 config.systemd.package
234 config.programs.ssh.package
235 gnused
236 miniserve
237 ];
238 execer = with pkgs; [
239 "cannot:${lib.getExe' rpm "rpm2cpio"}"
240 "cannot:${lib.getExe' squashfsTools "unsquashfs"}"
241 "cannot:${lib.getExe' unzip "unzip"}"
242 "cannot:${lib.getExe cfg.programs.git.package}"
243 "cannot:${lib.getExe cpio}"
244 "cannot:${lib.getExe' magic-wormhole "wormhole"}"
245 "cannot:${lib.getExe' fuse "fusermount"}"
246 "cannot:${lib.getExe less}"
247 "cannot:${lib.getExe' config.systemd.package "systemctl"}"
248 "cannot:${lib.getExe config.programs.ssh.package}"
249 ];
250 wrapper = with pkgs; [
251 "${lib.getExe' magickWrapped "magick"}:${lib.getExe' imagemagick "magick"}"
252 ];
253 fake = {
254 builtin = ["print"];
255 external = ["sudo" "umount"];
256 };
257 };
258 };
259 };
260 magickWrapped = pkgs.symlinkJoin {
261 inherit (pkgs.imagemagick) name;
262 paths = [ pkgs.imagemagick ];
263
264 buildInputs = with pkgs; [ makeWrapper ];
265 postBuild = ''
266 wrapProgram $out/bin/magick \
267 --prefix PATH : ${lib.makeBinPath (with pkgs; [ ghostscript ])}
268 '';
269 };
270 in ''
271 source ${zshrc}/share/zshrc
204 ''; 272 '';
205 zsh.dirHashes = let 273 zsh.dirHashes = let
206 flakeHashes = mapAttrs' (n: v: nameValuePair (inputNames.${n} or n) (toString v)) flakeInputs; 274 flakeHashes = mapAttrs' (n: v: nameValuePair (inputNames.${n} or n) (toString v)) flakeInputs;
207 inputNames = { 275 inputNames = {
208 "nixpkgs" = "nixos";
209 }; 276 };
210 in flakeHashes // { 277 in flakeHashes // {
211 u2w = "$HOME/projects/uni2work"; 278 u2w = "$HOME/projects/uni2work";
@@ -217,6 +284,16 @@ in {
217 pro = "$HOME/projects/pro"; 284 pro = "$HOME/projects/pro";
218 media = "$HOME/media"; 285 media = "$HOME/media";
219 }; 286 };
287 jq.colors = {
288 arrays = "1;37";
289 "false" = "0;37";
290 "null" = "2;37";
291 numbers = "0;37";
292 objectKeys = "1;34";
293 objects = "1;37";
294 strings = "0;32";
295 "true" = "0;37";
296 };
220 297
221 obs-studio = { 298 obs-studio = {
222 enable = true; 299 enable = true;
@@ -226,7 +303,7 @@ in {
226 gh = { 303 gh = {
227 enable = true; 304 enable = true;
228 settings = { 305 settings = {
229 editor = "${config.home-manager.users.${userName}.programs.emacs.package}/bin/emacsclient"; 306 editor = lib.getExe' editor "emacsclient";
230 gitProtocol = "ssh"; 307 gitProtocol = "ssh";
231 }; 308 };
232 }; 309 };
@@ -252,16 +329,10 @@ in {
252 # notify_on_cmd_finish = "invisible 120"; 329 # notify_on_cmd_finish = "invisible 120";
253 }; 330 };
254 keybindings = { 331 keybindings = {
255 "kitty_mod+n" = "detach_window"; 332 "kitty_mod+n" = "new_os_window_with_cwd";
256 "kitty_mod+m" = "detach_window ask"; 333 "kitty_mod+m" = "detach_window ask";
257 }; 334 "kitty_mod+enter" = "new_window_with_cwd";
258 }; 335 "kitty_mod+t" = "new_tab_with_cwd";
259 wpaperd = {
260 enable = true;
261 settings.default = {
262 path = "~/.wallpapers";
263 duration = "8h";
264 mode = "center";
265 }; 336 };
266 }; 337 };
267 fuzzel = { 338 fuzzel = {
@@ -274,7 +345,7 @@ in {
274 font = "Fira Sans"; 345 font = "Fira Sans";
275 }; 346 };
276 colors = { 347 colors = {
277 background = "000000aa"; 348 background = "000000cc";
278 text = "cdd6f4ff"; 349 text = "cdd6f4ff";
279 match = "94e2d5ff"; 350 match = "94e2d5ff";
280 selection = "585b70ff"; 351 selection = "585b70ff";
@@ -287,26 +358,46 @@ in {
287 }; 358 };
288 }; 359 };
289 }; 360 };
361 pandoc = {
362 enable = true;
363 extraAbbreviations = ["i.A." "d.h." "D.h." "gdw."];
364 };
365 nushell = {
366 enable = true;
367 settings.show_banner = false;
368 };
369 fd.enable = true;
290 }; 370 };
291 371
292 services = { 372 services = {
373 wpaperd = {
374 enable = true;
375 settings.default = {
376 path = "~/.wallpapers";
377 duration = "15m";
378 mode = "center";
379 };
380 };
293 emacs = { 381 emacs = {
294 enable = true; 382 enable = true;
295 socketActivation.enable = true; 383 socketActivation.enable = true;
296 client = { 384 client = {
297 enable = true; 385 enable = true;
298 arguments = mkForce ["--reuse-frame" "--alternate-editor" "\"\""]; 386 arguments = mkForce ["--create-frame" "--alternate-editor" (lib.getExe cfg.services.emacs.package)];
299 }; 387 };
300 }; 388 };
301 gpg-agent = { 389 gpg-agent = {
302 enable = true; 390 enable = true;
303 enableSshSupport = true; 391 enableSshSupport = true;
304 extraConfig = '' 392 extraConfig = ''
305 pinentry-program ${pkgs.pinentry-gtk2}/bin/pinentry 393 pinentry-program ${lib.getExe' pkgs.pinentry-gtk2 "pinentry"}
306 grab 394 grab
307 ''; 395 '';
308 }; 396 };
309 xembed-sni-proxy.enable = true; 397 xembed-sni-proxy = {
398 enable = true;
399 package = pkgs.kdePackages.plasma-workspace;
400 };
310 udiskie = { 401 udiskie = {
311 enable = true; 402 enable = true;
312 automount = false; 403 automount = false;
@@ -319,6 +410,7 @@ in {
319 }; 410 };
320 device_config = [ 411 device_config = [
321 { loop_file = "/nix/store/*-etc-metadata.erofs"; is_mounted = false; ignore = true; } 412 { loop_file = "/nix/store/*-etc-metadata.erofs"; is_mounted = false; ignore = true; }
413 { mount_path = "/run/nixos-etc-metadata"; ignore = true; }
322 { mount_path = "/run/nixos-etc-metadata.*"; ignore = true; } 414 { mount_path = "/run/nixos-etc-metadata.*"; ignore = true; }
323 ]; 415 ];
324 icon_names.media = ["drive-removable-media-symbolic"]; 416 icon_names.media = ["drive-removable-media-symbolic"];
@@ -338,7 +430,7 @@ in {
338 batch = "true"; 430 batch = "true";
339 log = "false"; 431 log = "false";
340 repeat = "watch"; 432 repeat = "watch";
341 sshcmd = "${pkgs.openssh}/bin/ssh"; 433 sshcmd = lib.getExe' pkgs.openssh "ssh";
342 ui = "text"; 434 ui = "text";
343 }; 435 };
344 }; 436 };
@@ -358,31 +450,17 @@ in {
358 enable = true; 450 enable = true;
359 events = [ 451 events = [
360 { event = "before-sleep"; command = lockCommand; } 452 { event = "before-sleep"; command = lockCommand; }
361 # { event = "after-resume"; command = "${cfg.wayland.windowManager.hyprland.package}/bin/hyprctl dispatch dpms on"; }
362 { event = "lock"; command = lockCommand; } 453 { event = "lock"; command = lockCommand; }
363 ]; 454 ];
364 timeouts = [ 455 timeouts = [
365 # { timeout = 300; 456 { timeout = 600; command = lockCommand; }
366 # command = "${cfg.wayland.windowManager.hyprland.package}/bin/hyprctl dispatch dpms off";
367 # }
368 { timeout = 330; command = lockCommand; }
369 ]; 457 ];
370 extraArgs = [ 458 extraArgs = [
459 "-w"
371 "idlehint" "30" 460 "idlehint" "30"
372 ]; 461 ];
373 }; 462 };
374 poweralertd.enable = true; 463 poweralertd.enable = true;
375 avizo = {
376 enable = true;
377 settings.default = {
378 time = "1.0";
379 background = "rgba(0, 0, 0, 0.8)";
380 border-color = "rgba(0, 0, 0, 1)";
381 bar-fg-color = "rgba(160, 160, 160, 1)";
382 bar-bg-color = "rgba(32, 32, 32, 0.96)";
383 # y-offset = "0.25";
384 };
385 };
386 }; 464 };
387 465
388 home.pointerCursor = { 466 home.pointerCursor = {
@@ -414,6 +492,13 @@ in {
414 }; 492 };
415 }; 493 };
416 494
495 qt.kde.settings = {
496 kwalletrc = {
497 KSecretD.Enabled = false;
498 Wallet."Default Wallet" = "store";
499 };
500 };
501
417 xsession.preferStatusNotifierItems = true; 502 xsession.preferStatusNotifierItems = true;
418 503
419 xresources.properties = import ./xresources.nix; 504 xresources.properties = import ./xresources.nix;
@@ -422,20 +507,19 @@ in {
422 packages = with pkgs; [ 507 packages = with pkgs; [
423 fira fira-code pwvucontrol wrappedKeepassxc wl-clipboard-rs 508 fira fira-code pwvucontrol wrappedKeepassxc wl-clipboard-rs
424 mumble pulseaudio-ctl pamixer libnotify screen-message 509 mumble pulseaudio-ctl pamixer libnotify screen-message
425 wrappedYTMDesktop libsForQt5.qt5ct playerctl evince 510 wrappedYTMDesktop libsForQt5.qt5ct playerctl evince papers
426 thunderbird zoom-us xdg-desktop-portal steam steam-run 511 thunderbird zoom-us xdg-desktop-portal steam steam-run
427 wireshark virt-manager rclone cached-nix-shell worktime 512 wireshark virt-manager rclone cached-nix-shell worktime
428 fira-code-symbols libreoffice xournalpp google-chrome 513 fira-code-symbols libreoffice xournalpp
429 nixos-shell virt-viewer freerdp gnome-icon-theme 514 nixos-shell virt-viewer freerdp gnome-icon-theme
430 paper-icon-theme sshpassSecret weechat element-desktop 515 paper-icon-theme sshpassSecret weechat element-desktop
431 matrix-synapse-tools.synadm 516 sieve-connect gimp3 inkscape udiskie glab nitrokey-app
432 flakeInputs.deploy-rs.packages.${config.nixpkgs.system}.deploy-rs
433 sieve-connect gimp inkscape udiskie glab nitrokey-app
434 pynitrokey gtklock wlrctl remmina openscad spice-record 517 pynitrokey gtklock wlrctl remmina openscad spice-record
435 libguestfs-with-appliance nerd-fonts.fira-mono 518 libguestfs-with-appliance nerd-fonts.fira-mono
436 nerd-fonts.symbols-only nerd-fonts.fira-code powerline-fonts 519 nerd-fonts.symbols-only nerd-fonts.fira-code powerline-fonts
437 swtpm 520 swtpm (hunspell.withDicts (dicts: with dicts; [en_GB-large de_DE]))
438 ]; 521 libation libqalculate
522 ] ++ mapAttrsToList (_name: pkg: pkgs.callPackage pkg {}) (customUtils.nixImport { dir = ./utils; });
439 523
440 file = { 524 file = {
441 ".backup-munin".source = ./backup-patterns; 525 ".backup-munin".source = ./backup-patterns;
@@ -455,12 +539,9 @@ in {
455 QT_QPA_PLATFORMTHEME = "qt5ct"; 539 QT_QPA_PLATFORMTHEME = "qt5ct";
456 LIBVIRT_DEFAULT_URI = "qemu:///system"; 540 LIBVIRT_DEFAULT_URI = "qemu:///system";
457 STACK_XDG = 1; 541 STACK_XDG = 1;
458 EDITOR = pkgs.writeShellScript "editor" '' 542 EDITOR = lib.getExe' editor "emacsclient";
459 args=("--reuse-frame" "--alternate-editor" "") 543 RCLONE_PASSWORD_COMMAND = "${lib.getExe' pkgs.libsecret "secret-tool"} lookup service rclone";
460 args+=("$@") 544 SYSTEMD_TINT_BACKGROUND = "false";
461 exec -a emacsclient ${cfg.services.emacs.package}/bin/emacsclient "''${args[@]}"
462 '';
463 RCLONE_PASSWORD_COMMAND = "${pkgs.libsecret}/bin/secret-tool lookup service rclone";
464 }; 545 };
465 546
466 extraProfileCommands = '' 547 extraProfileCommands = ''
@@ -473,7 +554,7 @@ in {
473 source = ./wireplumber; 554 source = ./wireplumber;
474 recursive = true; 555 recursive = true;
475 onChange = '' 556 onChange = ''
476 ${pkgs.systemd}/bin/systemctl --user try-restart wireplumber 557 ${lib.getExe' config.systemd.package "systemctl"} --user try-restart wireplumber
477 ''; 558 '';
478 }; 559 };
479 "stack/config.yaml" = { 560 "stack/config.yaml" = {
@@ -497,9 +578,17 @@ in {
497 General = { 578 General = {
498 dot_as_separator = 0; 579 dot_as_separator = 0;
499 }; 580 };
581 Mode = {
582 calculate_as_you_type = 1;
583 };
500 }; 584 };
501 }; 585 };
502 "emacs/init.el".source = ./emacs.el; 586 "emacs/init.el".source = pkgs.substitute {
587 src = ./emacs.el;
588 substitutions = [
589 "--subst-var-by" "ksshaskpass" (lib.getExe pkgs.kdePackages.ksshaskpass)
590 ];
591 };
503 "systemd/user/xdg-desktop-portal.service.d/after-graphical-session.conf".text = '' 592 "systemd/user/xdg-desktop-portal.service.d/after-graphical-session.conf".text = ''
504 [Unit] 593 [Unit]
505 After=graphical-session.target 594 After=graphical-session.target
@@ -508,34 +597,17 @@ in {
508 [Unit] 597 [Unit]
509 Before=graphical-session-pre.target 598 Before=graphical-session-pre.target
510 ''; 599 '';
600 "pdfpc/pdfpcrc".text = ''
601 mouse 8 prev
602 mouse 9 next
603 '';
511 }; 604 };
512 605
513 xdg.dataFile = { 606 xdg.dataFile = {
514 "pandoc/abbreviations" = {
515 source = pkgs.runCommand "pandoc-abbreviations" {
516 buildInputs = [ pkgs.pandoc pkgs.coreutils ];
517 } (let
518 germanAbbrevs = pkgs.fetchFromGitHub {
519 owner = "jfilter";
520 repo = "german-abbreviations";
521 rev = "8eb9dae93b6f05d7c53374cd217ab2dc89558e0c";
522 sha256 = "SaD3tSqzen6Y3SPICe6/9vhe4iMHlArZ3kFQaEk7Hps=";
523 };
524 in ''
525 cat \
526 <(pandoc --print-default-data-file=abbreviations) \
527 <(grep -E '^[^ ]+\.$' ${germanAbbrevs}/german_abbreviations.txt) \
528 ${pkgs.writeText "abbrevs.txt" ''
529 i.A.
530 d.h.
531 D.h.
532 gdw.
533 ''} \
534 | sort | uniq >$out
535 '');
536 };
537 "dbus-1/services/org.keepassxc.KeePassXC.service".source = "${wrappedKeepassxc}/share/dbus-1/services/org.keepassxc.KeePassXC.service"; 607 "dbus-1/services/org.keepassxc.KeePassXC.service".source = "${wrappedKeepassxc}/share/dbus-1/services/org.keepassxc.KeePassXC.service";
538 "dbus-1/services/org.freedesktop.secrets.service.service".source = "${wrappedKeepassxc}/share/dbus-1/services/org.freedesktop.secrets.service.service"; 608 "dbus-1/services/org.freedesktop.secrets.service.service".source = "${wrappedKeepassxc}/share/dbus-1/services/org.freedesktop.secrets.service.service";
609 "dbus-1/services/org.kde.kwalletd6.service".source = "${pkgs.kdePackages.kwallet}/share/dbus-1/services/org.kde.kwalletd6.service";
610 "dbus-1/services/org.kde.kwalletd5.service".source = "${pkgs.kdePackages.kwallet}/share/dbus-1/services/org.kde.kwalletd5.service";
539 "emoji-data/list.txt".source = pkgs.stdenv.mkDerivation { 611 "emoji-data/list.txt".source = pkgs.stdenv.mkDerivation {
540 inherit (sources.emoji-data) pname src; 612 inherit (sources.emoji-data) pname src;
541 version = lib.removePrefix "v" sources.emoji-data.version; 613 version = lib.removePrefix "v" sources.emoji-data.version;
@@ -621,19 +693,68 @@ in {
621 name = "Rainbow"; 693 name = "Rainbow";
622 exec = toString (pkgs.writeShellScript "rainbow" '' 694 exec = toString (pkgs.writeShellScript "rainbow" ''
623 exec -- \ 695 exec -- \
624 ${config.systemd.package}/bin/systemd-run --wait --user --slice-inherit \ 696 ${lib.getExe' config.systemd.package "systemd-run"} --wait --user --slice-inherit \
625 --property 'CPUAccounting=yes' --property 'CPUQuotaPeriodSec=50ms' \ 697 --property 'CPUAccounting=yes' --property 'CPUQuotaPeriodSec=50ms' \
626 --property 'Environment=DSCP=46' \ 698 -E DSCP=46 -E NIXOS_OZONE_WL \
627 -- ${pkgs.dscp}/bin/dscp ${pkgs.google-chrome}/bin/google-chrome-stable \ 699 -- ${lib.getExe pkgs.dscp} ${lib.getExe cfg.programs.chromium.package} \
628 --force-device-scale-factor=1.5 \
629 --class=Rainbow \ 700 --class=Rainbow \
630 --kiosk "https://web.openrainbow.com" \ 701 --app="https://web.openrainbow.com" \
631 --user-data-dir=''${HOME}/.config/google-chrome-rainbow 702 --user-data-dir=''${HOME}/.config/chromium-rainbow
632 ''); 703 '');
633 icon = pkgs.fetchurl { 704 icon = pkgs.fetchurl {
634 url = "https://web.openrainbow.com/rb/2.139.17/assets/skins/rainbow/images/homepage/logo__rainbow.svg"; 705 url = "https://web.openrainbow.com/rb/2.139.17/assets/skins/rainbow/images/homepage/logo__rainbow.svg";
635 hash = "sha256-5fmo8rDqVDpzkGaPjk4Y+SsSZpAsY7VUQSFW6WdHwuU="; 706 hash = "sha256-5fmo8rDqVDpzkGaPjk4Y+SsSZpAsY7VUQSFW6WdHwuU=";
636 }; 707 };
708 settings = {
709 StartupWMClass = "Rainbow";
710 };
711 };
712 kimai = {
713 name = "Kimai";
714 exec = toString (pkgs.writeShellScript "kimai" ''
715 exec -- \
716 ${lib.getExe cfg.programs.chromium.package} \
717 --class=Kimai \
718 --app="https://kimai.yggdrasil.li" \
719 --user-data-dir=''${HOME}/.config/chromium-kimai
720 '');
721 icon = pkgs.fetchurl {
722 url = "https://www.kimai.org/images/kimai_logo.png";
723 hash = "sha256-lnlOttzR2SwXA70R+egJUkeKr4U5V0avqTk8uX4bqfs=";
724 };
725 settings = {
726 StartupWMClass = "Kimai";
727 StartupNotify = "true";
728 };
729 };
730 audiobookshelf = {
731 name = "Audiobookshelf";
732 exec = toString (pkgs.writeShellScript "audiobookshelf" ''
733 exec -- \
734 ${lib.getExe cfg.programs.chromium.package} \
735 --class=Audiobookshelf \
736 --app="https://audiobookshelf.yggdrasil.li" \
737 --user-data-dir=''${HOME}/.config/chromium-audiobookshelf
738 '');
739 icon = pkgs.fetchurl {
740 url = "https://www.audiobookshelf.org/Logo.png";
741 hash = "sha256-JGPk+WNT1C4DC4lSMb0K0YmAMT5LvmSOeO0QRzkc7Lk=";
742 };
743 settings = {
744 StartupWMClass = "Audiobookshelf";
745 StartupNotify = "true";
746 };
747 };
748 thunderbird-lmu = {
749 name = "Thunderbird (LMU)";
750 exec = "thunderbird --name thunderbird -P lmu %U";
751 icon = "thunderbird";
752 genericName = "Email Client";
753 categories = [ "Network" "Chat" "Email" "Feed" "GTK" "News" ];
754 settings = {
755 StartupWMClass = "thunderbird";
756 StartupNotify = "true";
757 };
637 }; 758 };
638 }; 759 };
639 760
diff --git a/accounts/gkleen@sif/emacs.el b/accounts/gkleen@sif/emacs.el
index 5cee16b0..3beefba6 100644
--- a/accounts/gkleen@sif/emacs.el
+++ b/accounts/gkleen@sif/emacs.el
@@ -51,7 +51,7 @@
51 51
52;; (require 'scratch) 52;; (require 'scratch)
53(global-set-key (kbd "C-x B") 'scratch-create) 53(global-set-key (kbd "C-x B") 'scratch-create)
54(setq initial-major-mode 'scratch-mode) 54;; (setq initial-major-mode 'scratch-mode)
55(setq initial-scratch-message "") 55(setq initial-scratch-message "")
56 56
57(global-set-key (kbd "C-x K") 'kill-current-buffer) 57(global-set-key (kbd "C-x K") 'kill-current-buffer)
@@ -254,3 +254,5 @@ necessarily running."
254(bind-key "C-x C-m" #'move-file) 254(bind-key "C-x C-m" #'move-file)
255 255
256(let ((ssh_auth_sock (string-chop-newline (shell-command-to-string "gpgconf --list-dirs agent-ssh-socket")))) (setenv "SSH_AUTH_SOCK" ssh_auth_sock)) 256(let ((ssh_auth_sock (string-chop-newline (shell-command-to-string "gpgconf --list-dirs agent-ssh-socket")))) (setenv "SSH_AUTH_SOCK" ssh_auth_sock))
257(setenv "SSH_ASKPASS_REQUIRE" "prefer")
258(setenv "SSH_ASKPASS" "@ksshaskpass@")
diff --git a/accounts/gkleen@sif/firefox-chrome.css b/accounts/gkleen@sif/firefox-chrome.css
index 8900e2b9..726f1e4b 100644
--- a/accounts/gkleen@sif/firefox-chrome.css
+++ b/accounts/gkleen@sif/firefox-chrome.css
@@ -4,6 +4,21 @@
4 font-size:12px; 4 font-size:12px;
5} 5}
6 6
7#sidebar-main:has([expanded]) {
8 min-width:20em !important;
9 max-width:20em !important;
10}
11
12#sidebar, #sidebar-box {
13 min-width:35em !important;
14 max-width:35em !important;
15}
16
17#sidebar-box {
18 margin-right: var(--space-small);
19}
20
21/*
7#sidebar { 22#sidebar {
8 min-width:20em !important; 23 min-width:20em !important;
9 max-width:20em !important; 24 max-width:20em !important;
@@ -19,8 +34,21 @@
19} 34}
20 35
21#toolbar-menubar[inactive="true"] + #TabsToolbar { 36#toolbar-menubar[inactive="true"] + #TabsToolbar {
22 visibility: collapse !important; 37 visibility: collapse !important;
23} 38}
24 39
25#sidebar-box[sidebarcommand="tabcenter-reborn_ariasuni-sidebar-action"] #sidebar-header { visibility: collapse !important; } 40#sidebar-box[sidebarcommand="tabcenter-reborn_ariasuni-sidebar-action"] #sidebar-header { visibility: collapse !important; }
26#sidebar-box[sidebarcommand="_3c078156-979c-498b-8990-85f7987dd929_-sidebar-action"] #sidebar-header { visibility: collapse !important; } 41#sidebar-box[sidebarcommand="_3c078156-979c-498b-8990-85f7987dd929_-sidebar-action"] #sidebar-header { visibility: collapse !important; }
42*/
43
44.titlebar-buttonbox-container{ display: none; }
45#vertical-spacer { display: none; }
46#tabbrowser-tabs[orient="vertical"] .tab-background {
47 border-radius: var(--border-radius-small) !important;
48}
49hbox:has(> #tabs-newtab-button) {
50 display: none;
51}
52#sidebar-main .tools-and-extensions {
53 justify-content: space-around !important;
54}
diff --git a/accounts/gkleen@sif/niri/default.nix b/accounts/gkleen@sif/niri/default.nix
index bf124211..35a3d799 100644
--- a/accounts/gkleen@sif/niri/default.nix
+++ b/accounts/gkleen@sif/niri/default.nix
@@ -1,12 +1,16 @@
1{ config, hostConfig, pkgs, lib, ... }: 1{ config, hostConfig, pkgs, lib, flakeInputs, ... }:
2let 2let
3 niri = config.programs.niri.package; 3 cfg = config.programs.niri;
4
5 kdl = flakeInputs.niri-flake.lib.kdl;
6 sleaf = name: arg: kdl.node name [arg] [];
7
8 niri = cfg.package;
4 terminal = lib.getExe config.programs.kitty.package; 9 terminal = lib.getExe config.programs.kitty.package;
5 lightctl = lib.getExe' config.services.avizo.package "lightctl";
6 volumectl = lib.getExe' config.services.avizo.package "volumectl";
7 makoctl = lib.getExe' config.services.mako.package "makoctl"; 10 makoctl = lib.getExe' config.services.mako.package "makoctl";
8 loginctl = lib.getExe' hostConfig.systemd.package "loginctl"; 11 loginctl = lib.getExe' hostConfig.systemd.package "loginctl";
9 systemctl = lib.getExe' hostConfig.systemd.package "systemctl"; 12 systemctl = lib.getExe' hostConfig.systemd.package "systemctl";
13 swayosd-client = lib.getExe' config.services.swayosd.package "swayosd-client";
10 14
11 focus_or_spawn = pkgs.writeShellApplication { 15 focus_or_spawn = pkgs.writeShellApplication {
12 name = "focus-or-spawn"; 16 name = "focus-or-spawn";
@@ -29,7 +33,15 @@ let
29 33
30 while IFS=$'\n' read -r window_json; do 34 while IFS=$'\n' read -r window_json; do
31 if [[ -n $(jq -c "$window_select" <<<"$window_json") ]]; then 35 if [[ -n $(jq -c "$window_select" <<<"$window_json") ]]; then
32 niri msg action focus-window --id "$(jq -r '.id' <<<"$window_json")" 36 if jq -e '.is_focused' <<<"$window_json" >/dev/null; then
37 niri msg action focus-workspace-previous
38 else
39 if [[ $(jq -r --arg workspace_name "$workspace_name" 'map(select(.name == $workspace_name)) | .[0].is_focused' <<<"$workspaces_json") != "true" ]] && [[ $(jq -r --arg workspace_name "$workspace_name" 'map(select(.name == $workspace_name)) | .[0].id' <<<"$workspaces_json") = $(jq -r '.workspace_id' <<<"$window_json") ]]; then
40 niri msg action focus-workspace "$workspace_name"
41 else
42 niri msg action focus-window --id "$(jq -r '.id' <<<"$window_json")"
43 fi
44 fi
33 exit 0 45 exit 0
34 fi 46 fi
35 done < <(niri msg -j windows | jq -c '.[]') 47 done < <(niri msg -j windows | jq -c '.[]')
@@ -38,7 +50,6 @@ let
38 ''; 50 '';
39 }; 51 };
40 focus-or-spawn-action = config.lib.niri.actions.spawn (lib.getExe focus_or_spawn); 52 focus-or-spawn-action = config.lib.niri.actions.spawn (lib.getExe focus_or_spawn);
41 focus-or-spawn-action-app_id = app_id: focus-or-spawn-action ''select(.app_id == "${app_id}")'';
42 53
43 with_adjacent_workspace = pkgs.writeShellApplication { 54 with_adjacent_workspace = pkgs.writeShellApplication {
44 name = "with-adjacent-workspace"; 55 name = "with-adjacent-workspace";
@@ -75,9 +86,9 @@ let
75 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET" 86 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
76 ''; 87 '';
77 }; 88 };
78 with-adjacent-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_adjacent_workspace) "^pwctl|kpxc|bmgr|edit|term$"; 89 with-adjacent-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_adjacent_workspace) "^${lib.concatMapStringsSep "|" ({ name, ...}: name) cfg.scratchspaces}$";
79 focus-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}''; 90 focus-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
80 move-column-to-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}}}}''; 91 move-column-to-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}, "focus": true}}}'';
81 92
82 with_unnamed_workspace = pkgs.writeShellApplication { 93 with_unnamed_workspace = pkgs.writeShellApplication {
83 name = "with-unnamed-workspace"; 94 name = "with-unnamed-workspace";
@@ -90,13 +101,29 @@ let
90 active_output="$(jq -r '.[] | select(.is_focused) | .output' <<<"$workspaces_json")" 101 active_output="$(jq -r '.[] | select(.is_focused) | .output' <<<"$workspaces_json")"
91 active_workspace="$(jq -r '.[] | select(.is_focused) | .id' <<<"$workspaces_json")" 102 active_workspace="$(jq -r '.[] | select(.is_focused) | .id' <<<"$workspaces_json")"
92 103
93 workspace_json="$(jq -c --arg active_output "$active_output" 'map(select(.output == $active_output and .name == null)) | sort_by(.idx) | .[0]' <<<"$workspaces_json")" 104 history_json="$(socat STDIO UNIX-CONNECT:"$XDG_RUNTIME_DIR"/niri-workspace-history.sock)"
105 workspace_json="$(jq -c --arg active_output "$active_output" --argjson history "$history_json" 'map(select(.output == $active_output and .name == null)) | map({"value": ., "history_idx": ((. as $workspace | ($history[$active_output] | index($workspace | .id))) as $active_idx | if $active_idx then $active_idx else ($history[$active_output] | length) + 1 end)}) | sort_by(.history_idx, .value.idx) | map(.value) | .[0]' <<<"$workspaces_json")"
94 [[ -n $workspace_json && $workspace_json != null ]] || exit 0 106 [[ -n $workspace_json && $workspace_json != null ]] || exit 0
95 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET" 107 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
96 ''; 108 '';
97 }; 109 };
98 with-unnamed-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_unnamed_workspace); 110 with-unnamed-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_unnamed_workspace);
99 111
112 with_empty_unnamed_workspace = pkgs.writeShellApplication {
113 name = "with-empty-unnamed-workspace";
114 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
115 text = ''
116 action="$1"
117 shift
118
119 workspaces_json="$(niri msg -j workspaces)"
120 active_output="$(jq '.[] | select(.is_focused) | .output' <<<"$workspaces_json")"
121 target_workspace_id="$(jq --argjson active_output "$active_output" 'map(select(.active_window_id == null and .name == null and .output == $active_output)) | sort_by(.idx) | .[0].id' <<<"$workspaces_json")"
122 jq --argjson workspace_id "$target_workspace_id" -nc "$action" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
123 '';
124 };
125 with-empty-unnamed-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_empty_unnamed_workspace);
126
100 with_select_window = pkgs.writeShellApplication { 127 with_select_window = pkgs.writeShellApplication {
101 name = "with-select-window"; 128 name = "with-select-window";
102 runtimeInputs = [ niri pkgs.gojq pkgs.socat config.programs.fuzzel.package pkgs.gawk ]; 129 runtimeInputs = [ niri pkgs.gojq pkgs.socat config.programs.fuzzel.package pkgs.gawk ];
@@ -108,7 +135,7 @@ let
108 135
109 windows_json="$(niri msg -j windows)" 136 windows_json="$(niri msg -j windows)"
110 active_workspace="$(jq -r '.[] | select(.is_focused) | .workspace_id' <<<"$windows_json")" 137 active_workspace="$(jq -r '.[] | select(.is_focused) | .workspace_id' <<<"$windows_json")"
111 window_ix="$(gojq -r --arg active_workspace "$active_workspace" '.[] | select('"$window_select"') | "\(.title)\u0000icon\u001f\(.app_id)"' <<<"$windows_json" | fuzzel --log-level=warning --dmenu --index)" 138 window_ix="$(gojq -r --arg active_workspace "$active_workspace" '.[] | select('"$window_select"') | "\(.title)\u0000icon\u001f\(.app_id)"' <<<"$windows_json" | fuzzel --width=60 --log-level=warning --dmenu --index)"
112 # shellcheck disable=SC2016 139 # shellcheck disable=SC2016
113 window_json="$(gojq -rc --arg active_workspace "$active_workspace" --arg window_ix "$window_ix" 'map(select('"$window_select"')) | .[($window_ix | tonumber)]' <<<"$windows_json")" 140 window_json="$(gojq -rc --arg active_workspace "$active_workspace" --arg window_ix "$window_ix" 'map(select('"$window_select"')) | .[($window_ix | tonumber)]' <<<"$windows_json")"
114 141
@@ -118,12 +145,91 @@ let
118 ''; 145 '';
119 }; 146 };
120 with-select-window-action = config.lib.niri.actions.spawn (lib.getExe with_select_window); 147 with-select-window-action = config.lib.niri.actions.spawn (lib.getExe with_select_window);
148
149 with_predicate_window = pred: pkgs.writeShellApplication {
150 name = "with-predicate-window";
151 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
152 text = ''
153 action="$1"
154 shift
155
156 windows_json="$(niri msg -j windows)"
157 window_json="$(gojq -rc 'map(select(${pred})) | .[0]' <<<"$windows_json")"
158
159 [[ -z "$window_json" || $window_json = "null" ]] && exit 1
160
161 jq -c "$action" <<<"$window_json" | socat STDIO "$NIRI_SOCKET"
162 '';
163 };
164
165 with-urgent-window-action = config.lib.niri.actions.spawn (lib.getExe (with_predicate_window ".is_urgent"));
166 with-focused-window-action = config.lib.niri.actions.spawn (lib.getExe (with_predicate_window ".is_focused"));
121in { 167in {
122 imports = [ 168 imports = [
123 ./waybar.nix 169 ./waybar.nix
124 ./mako.nix 170 ./mako.nix
171 ./swayosd.nix
125 ]; 172 ];
126 173
174 options = {
175 programs.niri.scratchspaces = lib.mkOption {
176 type = lib.types.listOf (lib.types.submodule ({ config, ... }: {
177 options = {
178 name = lib.mkOption {
179 type = lib.types.str;
180 };
181 match = lib.mkOption {
182 type = lib.types.listOf (lib.types.attrsOf kdl.types.kdl-args);
183 default = [];
184 };
185 exclude = lib.mkOption {
186 type = lib.types.listOf (lib.types.attrsOf kdl.types.kdl-args);
187 default = [];
188 };
189 windowRuleExtra = lib.mkOption {
190 type = kdl.types.kdl-nodes;
191 default = [];
192 };
193 key = lib.mkOption {
194 type = lib.types.nullOr lib.types.str;
195 default = null;
196 };
197 moveKey = lib.mkOption {
198 type = lib.types.nullOr lib.types.str;
199 default = let
200 keys = lib.splitString "+" config.key;
201 defMoveKey = lib.concatStringsSep "+" (lib.flatten [
202 (lib.take (lib.length keys - 1) keys)
203 ["Shift"]
204 (lib.takeEnd 1 keys)
205 ]);
206 in if config.key == null then null else defMoveKey;
207 };
208 spawn = lib.mkOption {
209 type = lib.types.nullOr (lib.types.listOf lib.types.str);
210 default = null;
211 };
212 app-id = lib.mkOption {
213 type = lib.types.nullOr lib.types.str;
214 default = null;
215 };
216 selector = lib.mkOption {
217 type = lib.types.nullOr lib.types.str;
218 default = null;
219 };
220 };
221
222 config = lib.mkMerge [
223 (lib.mkIf (config.app-id != null) {
224 match = lib.mkDefault [ { app-id = "^${lib.escapeRegex config.app-id}$"; } ];
225 selector = lib.mkDefault "select(.app_id == \"${config.app-id}\")";
226 })
227 ];
228 }));
229 default = [];
230 };
231 };
232
127 config = { 233 config = {
128 systemd.user.services.xwayland-satellite = { 234 systemd.user.services.xwayland-satellite = {
129 Unit = { 235 Unit = {
@@ -150,461 +256,754 @@ in {
150 { event = "after-resume"; command = "${lib.getExe niri} msg action power-on-monitors"; } 256 { event = "after-resume"; command = "${lib.getExe niri} msg action power-on-monitors"; }
151 ]; 257 ];
152 timeouts = [ 258 timeouts = [
153 { timeout = 300; 259 { timeout = 540;
154 command = "${lib.getExe niri} msg action power-off-monitors"; 260 command = "${lib.getExe niri} msg action power-off-monitors";
155 } 261 }
156 ]; 262 ];
157 }; 263 };
158 264
159 programs.niri.settings = { 265 systemd.user.sockets.niri-workspace-history = {
160 prefer-no-csd = true; 266 Socket = {
161 screenshot-path = "${config.home.homeDirectory}/screenshots"; 267 ListenStream = "%t/niri-workspace-history.sock";
162 268 SocketMode = "0600";
163 hotkey-overlay.skip-at-startup = true;
164
165 input = {
166 keyboard.xkb = {
167 layout = "us,us";
168 variant = "dvp,";
169 options = "compose:caps,grp:win_space_toggle";
170 };
171
172 workspace-auto-back-and-forth = true;
173 # focus-follows-mouse.enable = true;
174 warp-mouse-to-focus = true;
175 }; 269 };
176 270 };
177 outputs = { 271 systemd.user.services.niri-workspace-history = {
178 "eDP-1" = { 272 Unit = {
179 scale = 1.5; 273 BindsTo = [ "niri.service" ];
180 position = { x = 0; y = 0; }; 274 After = [ "niri.service" ];
181 };
182 "Ancor Communications Inc ASUS PB287Q 0x0000DD9B" = {
183 scale = 1.5;
184 position = { x = 2560; y = 0; };
185 };
186 "HP Inc. HP 727pu CN4417143K" = {
187 mode = { width = 2560; height = 1440; refresh = 119.998; };
188 scale = 1;
189 position = { x = 2560; y = 0; };
190 variable-refresh-rate = "on-demand";
191 };
192 }; 275 };
193 276 Install = {
194 environment = { 277 WantedBy = [ "niri.service" ];
195 NIXOS_OZONE_WL = "1";
196 QT_QPA_PLATFORM = "wayland";
197 QT_WAYLAND_DISABLE_WINDOWDECORATION = "1";
198 GDK_BACKEND = "wayland";
199 SDL_VIDEODRIVER = "wayland";
200 DISPLAY = ":0";
201 }; 278 };
202 279 Service = {
203 debug.render-drm-device = "/dev/dri/by-path/pci-0000:00:02.0-render"; 280 Type = "simple";
204 281 Sockets = [ "niri-workspace-history.socket" ];
205 layout = { 282 ExecStart = pkgs.writers.writePython3 "niri-workspace-history" { flakeIgnore = ["E501"]; } ''
206 gaps = 8; 283 import os
207 struts = { left = 0; right = 0; top = 0; bottom = 0; }; 284 import socket
208 focus-ring = { 285 import json
209 width = 2; 286 # import sys
210 active.gradient = { 287 from collections import defaultdict
211 from = "hsla(195 100% 60% 0.75)"; 288 from threading import Thread, Lock
212 to = "hsla(155 100% 50% 0.75)"; 289 from socketserver import StreamRequestHandler, ThreadingTCPServer
213 angle = 29; 290 from contextlib import contextmanager
214 relative-to = "workspace-view"; 291 from io import TextIOWrapper
215 }; 292
216 inactive.gradient = { 293
217 from = "hsla(0 0% 42% 0.66)"; 294 @contextmanager
218 to = "hsla(0 0% 35% 0.66)"; 295 def detaching(thing):
219 angle = 29; 296 try:
220 relative-to = "workspace-view"; 297 yield thing
221 }; 298 finally:
222 }; 299 thing.detach()
223 300
224 preset-column-widths = [ 301
225 { proportion = 1. / 4.; } 302 workspace_history = defaultdict(list)
226 { proportion = 1. / 3.; } 303 history_lock = Lock()
227 { proportion = 1. / 2.; } 304
228 { proportion = 2. / 3.; } 305
229 { proportion = 3. / 4.; } 306 def monitor_niri():
230 ]; 307 workspaces = list()
231 default-column-width.proportion = 1. / 2.; 308
232 preset-window-heights = [ 309 def focus_workspace(output, workspace):
233 { proportion = 1. / 3.; } 310 with history_lock:
234 { proportion = 1. / 2.; } 311 workspace_history[output] = [workspace] + [ws for ws in workspace_history[output] if ws != workspace]
235 { proportion = 2. / 3.; } 312 # print(json.dumps(workspace_history), file=sys.stderr)
236 { proportion = 1.; } 313
237 ]; 314 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
238 315 sock.connect(os.environ["NIRI_SOCKET"])
239 always-center-single-column = true; 316 sock.send(b"\"EventStream\"\n")
317 for line in sock.makefile(buffering=1, encoding='utf-8'):
318 if line_json := json.loads(line):
319 if "WorkspacesChanged" in line_json:
320 workspaces = line_json["WorkspacesChanged"]["workspaces"]
321 for ws in workspaces:
322 if ws["is_focused"]:
323 focus_workspace(ws["output"], ws["id"])
324 if "WorkspaceActivated" in line_json:
325 for ws in workspaces:
326 if ws["id"] != line_json["WorkspaceActivated"]["id"]:
327 continue
328 focus_workspace(ws["output"], ws["id"])
329 break
330
331
332 class RequestHandler(StreamRequestHandler):
333 def handle(self):
334 with detaching(TextIOWrapper(self.wfile, encoding='utf-8', write_through=True)) as out:
335 with history_lock:
336 json.dump(workspace_history, out)
337
338
339 class Server(ThreadingTCPServer):
340 def __init__(self):
341 ThreadingTCPServer.__init__(self, ("", 8000), RequestHandler, bind_and_activate=False)
342 self.socket = socket.fromfd(3, self.address_family, self.socket_type)
343
344
345 def run_server():
346 with Server() as server:
347 server.serve_forever()
348
349
350 niri = Thread(target=monitor_niri)
351 niri.daemon = True
352 niri.start()
353
354 server_thread = Thread(target=run_server)
355 server_thread.daemon = True
356 server_thread.start()
357
358 while True:
359 server_thread.join(timeout=0.5)
360 niri.join(timeout=0.5)
361
362 if not (niri.is_alive() and server_thread.is_alive()):
363 break
364 '';
240 }; 365 };
241 366 };
242 cursor.hide-when-typing = true; 367 systemd.user.services.niri-workspace-sort = {
243 368 Unit = {
244 input = { 369 BindsTo = [ "niri.service" ];
245 touchpad.enable = false; 370 After = [ "niri.service" ];
246 trackball = {
247 scroll-method = "on-button-down";
248 scroll-button = 278;
249 };
250 }; 371 };
251 372 Install = {
252 workspaces = { 373 WantedBy = [ "niri.service" ];
253 "001" = { name = "pwctl"; open-on-output = "eDP-1"; };
254 "002" = { name = "kpxc"; open-on-output = "eDP-1"; };
255 "003" = { name = "bmgr"; open-on-output = "eDP-1"; };
256 "004" = { name = "term"; open-on-output = "eDP-1"; };
257 "005" = { name = "edit"; open-on-output = "eDP-1"; };
258 "101".name = "comm";
259 "102".name = "web";
260 # "104".name = "read";
261 # "105".name = "mon";
262 "110".name = "vid";
263 "120".name = "bmr";
264 }; 374 };
265 375 Service = {
266 window-rules = [ 376 Type = "simple";
267 # { 377 ExecStart = pkgs.writers.writePython3 "niri-workspace-sort" { flakeIgnore = ["E501"]; } ''
268 # geometry-corner-radius = 378 import os
269 # let 379 import sys
270 # allCorners = r: { bottom-left = r; bottom-right = r; top-left = r; top-right = r; }; 380 import socket
271 # in allCorners 4.; 381 import json
272 # clip-to-geometry = true; 382
273 # } 383 outputs = None
274 { 384 only = {'HDMI-A-1': {'bmr'}, 'eDP-1': {'vid'}}
275 matches = [ { app-id = "^com\.saivert\.pwvucontrol$"; } ]; 385
276 open-on-workspace = "pwctl"; 386
277 open-maximized = true; 387 class Niri(socket.socket):
278 } 388 def __init__(self):
279 { 389 super().__init__(socket.AF_UNIX, socket.SOCK_STREAM)
280 matches = [ { app-id = "^\.blueman-manager-wrapped$"; } ]; 390 super().connect(os.environ["NIRI_SOCKET"])
281 open-on-workspace = "bmgr"; 391 self.fh = super().makefile(mode='rw', buffering=1, encoding='utf-8')
282 open-maximized = true; 392
283 } 393 def cmd(self, obj):
284 { 394 print(json.dumps(obj, separators=(',', ':')), flush=True, file=self.fh)
285 matches = [ { app-id = "^org\.keepassxc\.KeePassXC$"; } ]; 395
286 block-out-from = "screencast"; 396 def event_stream(self):
287 } 397 self.cmd("EventStream")
288 { 398 return self.fh
289 matches = [ { app-id = "^org\.keepassxc\.KeePassXC$"; } ]; 399
290 excludes = [ 400
291 { title = "^Unlock Database.*"; } 401 with Niri() as niri, Niri().event_stream() as niri_stream:
292 { title = "^Access Request.*"; } 402 for line in niri_stream:
293 { title = ".*Passkey credentials$"; } 403 workspaces = None
294 ]; 404 if line_json := json.loads(line):
295 open-on-workspace = "kpxc"; 405 if "WorkspacesChanged" in line_json:
296 open-maximized = true; 406 workspaces = line_json["WorkspacesChanged"]["workspaces"]
297 open-focused = false; 407
298 } 408 if workspaces is None:
299 { 409 continue
300 matches = [ 410
301 { app-id = "^org\.keepassxc\.KeePassXC$"; title = "^Unlock Database.*"; } 411 old_outputs = outputs
302 { app-id = "^org\.keepassxc\.KeePassXC$"; title = "^Access Request.*"; } 412 outputs = {ws["output"] for ws in workspaces}
303 { app-id = "^org\.keepassxc\.KeePassXC$"; title = ".*Passkey credentials$"; } 413 if old_outputs is None:
304 ]; 414 print("Initial outputs: {}".format(outputs), file=sys.stderr)
305 open-focused = true; 415 continue
306 } 416
307 { 417 new_outputs = outputs - old_outputs
308 matches = [ { app-id = "^kitty-scratch$"; } ]; 418 if not new_outputs:
309 open-on-workspace = "term"; 419 continue
310 open-maximized = true; 420 print("New outputs: {}".format(new_outputs), file=sys.stderr)
311 } 421
312 { 422 relevant_workspaces = list(filter(lambda ws: (ws["name"] is not None) or (ws["active_window_id"] is not None), workspaces))
313 matches = [ { title = "^scratch$"; app-id = "^emacs$"; } ]; 423 target_output = next(iter(outputs - set(only.keys())))
314 open-on-workspace = "edit"; 424 if not target_output:
315 open-maximized = true; 425 continue
316 } 426 for ws in relevant_workspaces:
317 { 427 ws_ident = ws["name"] if ws["name"] is not None else (ws["output"], ws["idx"])
318 matches = [ 428 if ws["output"] not in set(only.keys()):
319 { app-id = "^emacs$"; } 429 continue
320 { app-id = "^firefox$"; } 430 if ws_ident in only[ws["output"]]:
321 ]; 431 continue
322 default-column-width.proportion = 2. / 3.; 432
323 } 433 print("{} -> {}".format(ws_ident, target_output), file=sys.stderr)
324 { 434 niri.cmd({"Action": {"MoveWorkspaceToMonitor": {"reference": {"Id": ws["id"]}, "output": target_output}}})
325 matches = [ 435 '';
326 { app-id = "^kitty$"; } 436 Restart = "on-failure";
327 { app-id = "^kitty-play$"; } 437 RestartSec = 10;
328 ];
329 default-column-width.proportion = 1. / 3.;
330 }
331 {
332 matches = [
333 { app-id = "^thunderbird$"; }
334 { app-id = "^Element$"; }
335 ];
336 open-on-workspace = "comm";
337 }
338 {
339 matches = [ { app-id = "^firefox$"; } ];
340 open-on-workspace = "web";
341 open-maximized = true;
342 variable-refresh-rate = true;
343 }
344 # {
345 # matches = [
346 # { app-id = "^evince$"; }
347 # { app-id = "^imv$"; }
348 # { app-id = "^org\.pwmt\.zathura$"; }
349 # ];
350 # open-on-workspace = "read";
351 # }
352 {
353 matches = [ { app-id = "^mpv$"; } ];
354 open-on-workspace = "vid";
355 default-column-width.proportion = 1.;
356 variable-refresh-rate = true;
357 }
358 {
359 matches = [ { app-id = "^kitty-play$"; } ];
360 open-on-workspace = "vid";
361 open-focused = false;
362 }
363 # {
364 # matches = [
365 # { app-id = "^qemu$"; }
366 # { app-id = "^virt-manager$"; }
367 # ];
368 # open-on-workspace = "mon";
369 # }
370 {
371 matches = [ { app-id = "^pdfpc$"; } ];
372 default-column-width.proportion = 1.;
373 }
374 {
375 matches = [ { app-id = "^pdfpc$"; title = "^pdfpc - presentation"; } ];
376 open-on-workspace = "bmr";
377 open-fullscreen = true;
378 }
379 {
380 matches = [
381 { app-id = "^Gimp-"; title = "^Quit GIMP$"; }
382 { app-id = "^org\.kde\.polkit-kde-authentication-agent-1$"; }
383 ];
384 open-floating = true;
385 }
386 ];
387 layer-rules = [
388 { matches = [
389 { namespace = "^notifications$"; }
390 { namespace = "^waybar$"; }
391 ];
392 block-out-from = "screencast";
393 }
394 ];
395
396 binds = with config.lib.niri.actions; {
397 "Mod+Slash".action = show-hotkey-overlay;
398
399 "Mod+Return".action = spawn terminal;
400 "Mod+Q".action = close-window;
401 "Mod+O".action = spawn (lib.getExe config.programs.fuzzel.package);
402 "Mod+Shift+O".action = spawn (lib.getExe config.programs.fuzzel.package) "--list-executables-in-path";
403
404 "Mod+Alt+E".action = spawn (lib.getExe' config.services.emacs.package "emacsclient") "-c";
405 "Mod+Alt+Y".action = spawn (lib.getExe (pkgs.writeShellApplication {
406 name = "queue-yt-dlp";
407 runtimeInputs = with pkgs; [ wl-clipboard-rs socat ];
408 text = ''
409 socat STDIO UNIX-CONNECT:"$XDG_RUNTIME_DIR"/yt-dlp.sock <<<$'{ "urls": ["'"$(wl-paste)"$'"] }'
410 '';
411 }));
412 "Mod+Alt+L".action = spawn (lib.getExe (pkgs.writeShellApplication {
413 name = "queue-yt-dlp";
414 runtimeInputs = with pkgs; [ wl-clipboard-rs config.programs.kitty.package ];
415 text = ''
416 exec -- kitty --app-id kitty-play --directory "$HOME"/media mpv "$(wl-paste)"
417 '';
418 }));
419
420 "Mod+U".action = spawn (lib.getExe (pkgs.writeShellApplication {
421 name = "qalc-fuzzel";
422 runtimeInputs = with pkgs; [ wl-clipboard-rs libqalculate config.programs.fuzzel.package coreutils findutils libnotify gnugrep ];
423 text = ''
424 RESULTS_DIR="$HOME/.cache/qalc-fuzzel"
425 prev() {
426 FOUND=false
427 while IFS= read -r line; do
428 [[ -n "$line" ]] || continue
429 FOUND=true
430 echo "$line"
431 done < <(export LC_ALL=C.UTF-8; echo; find "$RESULTS_DIR" -type f -printf $'%T@ %p\n' | sort -n | cut -d' ' -f2- | xargs -r cat)
432 $FOUND || echo
433 }
434 FUZZEL_RES=$(prev | fuzzel --dmenu --prompt "qalc> ") || exit $?
435 if [[ "$FUZZEL_RES" =~ .*\ =\ .* ]]; then
436 QALC_RES="$FUZZEL_RES"
437 QALC_RET=0
438 else
439 QALC_RES=$(qalc "$FUZZEL_RES" 2>&1)
440 QALC_RET=$?
441 fi
442 [[ -n "$QALC_RES" ]] || exit 1
443 EXISTING=false
444 set +o pipefail
445 grep -Fxrl "$QALC_RES" "$RESULTS_DIR" | xargs -r touch
446 [[ ''${PIPESTATUS[0]} -eq 0 ]] && EXISTING=true
447 set -o pipefail
448 if [[ $QALC_RET -eq 0 ]] && ! $EXISTING; then
449 set +o pipefail
450 RES_FILE="$RESULTS_DIR"/$(date -uIs).$(tr -Cd 'a-zA-Z0-9' </dev/random | head -c 10)
451 set -o pipefail
452 cat >"$RES_FILE" <<<"$QALC_RES"
453 fi
454 [[ "$QALC_RES" =~ .*\ =\ (.*) ]] && QALC_RES="''${BASH_REMATCH[1]}"
455 [[ $QALC_RET -eq 0 ]] && wl-copy "$QALC_RES"
456 notify-send "$QALC_RES"
457 '';
458 }));
459 "Mod+E".action = spawn (lib.getExe (pkgs.writeShellApplication {
460 name = "emoji-fuzzel";
461 runtimeInputs = with pkgs; [ config.programs.fuzzel.package wtype wl-clipboard-rs ];
462 text = ''
463 FUZZEL_RES=$(fuzzel --dmenu --prompt "emoji> " <"$HOME"/.local/share/emoji-data/list.txt) || exit $?
464 [[ -n "$FUZZEL_RES" ]] || exit 1
465 wl-copy "$(cut -d ':' -f 1 <<<"$FUZZEL_RES" | tr -d '\n')" && wtype -k XF86Paste
466 '';
467 }));
468 "Print".action = spawn (lib.getExe (pkgs.writeShellApplication {
469 name = "screenshot";
470 runtimeInputs = with pkgs; [ grim slurp wl-clipboard-rs coreutils ];
471 text = ''
472 grim -g "$(slurp -b 00000080 -c FFFFFFFF -s 00000000 -w 1)" - \
473 | tee "$HOME/screenshots/$(date +"%Y-%m-%dT%H:%M:%S").png" \
474 | wl-copy --type image/png
475 '';
476 }));
477 "Shift+Print".action = spawn (lib.getExe (pkgs.writeShellApplication {
478 name = "screenshot";
479 runtimeInputs = with pkgs; [ grim niri gojq wl-clipboard-rs coreutils ];
480 text = ''
481 grim -o "$(niri msg -j workspaces | jq -r '.[] | select(.is_focused) | .output')" - \
482 | tee "$HOME/screenshots/$(date +"%Y-%m-%dT%H:%M:%S").png" \
483 | wl-copy --type image/png
484 '';
485 }));
486 "Mod+B".action = with-select-window-action ".workspace_id == ($active_workspace | tonumber)" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
487 "Mod+Shift+B".action = with-select-window-action "true" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
488
489 "Mod+H".action = focus-column-left;
490 "Mod+T".action = focus-window-down;
491 "Mod+N".action = focus-window-up;
492 "Mod+S".action = focus-column-right;
493
494 "Mod+Shift+H".action = move-column-left;
495 "Mod+Shift+T".action = move-window-down;
496 "Mod+Shift+N".action = move-window-up;
497 "Mod+Shift+S".action = move-column-right;
498
499 "Mod+Control+H".action = focus-monitor-left;
500 "Mod+Control+T".action = focus-monitor-down;
501 "Mod+Control+N".action = focus-monitor-up;
502 "Mod+Control+S".action = focus-monitor-right;
503
504 "Mod+Shift+Control+H".action = move-workspace-to-monitor-left;
505 "Mod+Shift+Control+T".action = move-workspace-to-monitor-down;
506 "Mod+Shift+Control+N".action = move-workspace-to-monitor-up;
507 "Mod+Shift+Control+S".action = move-workspace-to-monitor-right;
508
509 "Mod+G".action = focus-adjacent-workspace "down";
510 "Mod+C".action = focus-adjacent-workspace "up";
511
512 "Mod+Shift+G".action = move-column-to-adjacent-workspace "down";
513 "Mod+Shift+C".action = move-column-to-adjacent-workspace "up";
514
515 "Mod+Shift+Control+G".action = move-workspace-down;
516 "Mod+Shift+Control+C".action = move-workspace-up;
517
518 "Mod+ParenLeft".action = focus-workspace "comm";
519 "Mod+Shift+ParenLeft".action = move-column-to-workspace "comm";
520
521 "Mod+ParenRight".action = focus-workspace "web";
522 "Mod+Shift+ParenRight".action = move-column-to-workspace "web";
523
524 "Mod+BraceRight".action = focus-workspace "read";
525 "Mod+Shift+BraceRight".action = move-column-to-workspace "read";
526
527 "Mod+BraceLeft".action = focus-workspace "mon";
528 "Mod+Shift+BraceLeft".action = move-column-to-workspace "mon";
529
530 "Mod+Asterisk".action = focus-workspace "vid";
531 "Mod+Shift+Asterisk".action = move-column-to-workspace "vid";
532
533 "Mod+Plus".action = with-unnamed-workspace-action ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
534 "Mod+Shift+Plus".action = with-unnamed-workspace-action ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}}}}'';
535
536 "Mod+M".action = consume-or-expel-window-left;
537 "Mod+W".action = consume-or-expel-window-right;
538
539 "Mod+R".action = switch-preset-column-width;
540 "Mod+Shift+R".action = switch-preset-window-height;
541 "Mod+F".action = center-column;
542 "Mod+Shift+F".action = maximize-column;
543 "Mod+Shift+Ctrl+F".action = fullscreen-window;
544
545 "Mod+V".action = switch-focus-between-floating-and-tiling;
546 "Mod+Shift+V".action = toggle-window-floating;
547
548 "Mod+Left".action = set-column-width "-10%";
549 "Mod+Down".action = set-window-height "-10%";
550 "Mod+Up".action = set-window-height "+10%";
551 "Mod+Right".action = set-column-width "+10%";
552
553 "Mod+Shift+Z" = {
554 action = spawn (lib.getExe niri) "msg" "action" "power-off-monitors";
555 allow-when-locked = true;
556 };
557 "Mod+Shift+L".action = spawn loginctl "lock-session";
558 "Mod+Shift+E".action = quit;
559 "Mod+Shift+Minus" = {
560 action = spawn systemctl "suspend";
561 allow-when-locked = true;
562 };
563 "Mod+Shift+Control+Minus" = {
564 action = spawn systemctl "hibernate";
565 allow-when-locked = true;
566 };
567 "Mod+Shift+P" = {
568 action = spawn (lib.getExe pkgs.playerctl) "-a" "pause";
569 allow-when-locked = true;
570 };
571
572 "XF86MonBrightnessUp" = {
573 action = spawn lightctl "-d" "-e4" "-n1" "up";
574 allow-when-locked = true;
575 };
576 "XF86MonBrightnessDown" = {
577 action = spawn lightctl "-d" "-e4" "-n1" "down";
578 allow-when-locked = true;
579 };
580 "XF86AudioRaiseVolume" = {
581 action = spawn volumectl "-d" "-u" "up";
582 allow-when-locked = true;
583 };
584 "XF86AudioLowerVolume" = {
585 action = spawn volumectl "-d" "-u" "down";
586 allow-when-locked = true;
587 };
588 "XF86AudioMute" = {
589 action = spawn volumectl "-d" "toggle-mute";
590 allow-when-locked = true;
591 };
592 "XF86AudioMicMute" = {
593 action = spawn volumectl "-d" "-m" "toggle-mute";
594 allow-when-locked = true;
595 };
596
597 "Mod+Semicolon".action = spawn makoctl "dismiss" "--group";
598 "Mod+Shift+Semicolon".action = spawn makoctl "dismiss" "--all";
599 "Mod+Period".action = spawn makoctl "menu" (lib.getExe config.programs.fuzzel.package) "--dmenu";
600 "Mod+Comma".action = spawn makoctl "restore";
601
602 "Mod+Control+A".action = focus-or-spawn-action-app_id "com.saivert.pwvucontrol" "pwctl" "pwvucontrol";
603 "Mod+Control+P".action = focus-or-spawn-action-app_id "org.keepassxc.KeePassXC" "kpxc" "keepassxc";
604 "Mod+Control+B".action = focus-or-spawn-action-app_id ".blueman-manager-wrapped" "bmgr" "blueman-manager";
605 "Mod+Control+Return".action = focus-or-spawn-action-app_id "kitty-scratch" "term" "kitty" "--app-id" "kitty-scratch";
606 "Mod+Control+E".action = focus-or-spawn-action "select(.app_id == \"emacs\" and .title == \"scratch\")" "edit" "emacsclient" "-c" "--frame-parameters=(quote (name . \"scratch\"))";
607 }; 438 };
608 }; 439 };
440
441 programs.niri.scratchspaces = [
442 { name = "pwctl";
443 key = "Mod+Control+A";
444 spawn = ["pwvucontrol"];
445 app-id = "com.saivert.pwvucontrol";
446 }
447 { name = "kpxc";
448 exclude = [
449 { title = "^Unlock Database.*"; }
450 { title = "^Access Request.*"; }
451 { title = ".*Passkey credentials$"; }
452 ];
453 windowRuleExtra = with kdl; [
454 (sleaf "open-focused" false)
455 ];
456 key = "Mod+Control+P";
457 app-id = "org.keepassxc.KeePassXC";
458 spawn = [ "keepassxc" ];
459 }
460 { name = "bmgr";
461 key = "Mod+Control+B";
462 app-id = ".blueman-manager-wrapped";
463 spawn = [ "blueman-manager" ];
464 }
465 { name = "term";
466 key = "Mod+Control+Return";
467 app-id = "kitty-scratch";
468 spawn = [ "kitty" "--app-id" "kitty-scratch" ];
469 }
470 { name = "edit";
471 match = [ { title = "^scratch$"; app-id = "^emacs$"; } ];
472 key = "Mod+Control+E";
473 selector = "select(.app_id == \"emacs\" and .title == \"scratch\")";
474 spawn = [ "emacsclient" "-c" "--frame-parameters=(quote (name . \"scratch\"))" ];
475 }
476 { name = "eff";
477 key = "Mod+Control+O";
478 app-id = "com.github.wwmm.easyeffects";
479 spawn = [ "easyeffects" ];
480 }
481 { name = "time";
482 key = "Mod+Control+K";
483 app-id = "chrome-kimai.yggdrasil.li__-Default";
484 spawn = [ (toString (pkgs.resholve.writeScript "kimai" {
485 interpreter = pkgs.runtimeShell;
486 inputs = [ pkgs.dex ];
487 execer = [ "cannot:${lib.getExe pkgs.dex}" ];
488 } ''
489 exec dex $HOME/.local/state/nix/profile/share/applications/kimai.desktop
490 '')) ];
491 windowRuleExtra = with kdl; [
492 (sleaf "block-out-from" "screencast")
493 ];
494 }
495 ];
496 programs.niri.config =
497 let
498 inherit (kdl) node plain leaf flag;
499 optional-node = cond: v:
500 if cond
501 then v
502 else null;
503 opt-props = lib.filterAttrs (lib.const (value: value != null));
504 normalize-nodes = nodes: lib.remove null (lib.flatten nodes);
505 in
506 normalize-nodes [
507 (flag "prefer-no-csd")
508
509 (sleaf "screenshot-path" "~/screenshots/%Y-%m-%dT%H:%M:%S.png")
510
511 (plain "hotkey-overlay" [
512 (flag "skip-at-startup")
513 ])
514
515 (plain "input" [
516 (plain "keyboard" [
517 (sleaf "repeat-delay" 300)
518 (sleaf "repeat-rate" 50)
519
520 (plain "xkb" [
521 (sleaf "layout" "us,us")
522 (sleaf "variant" "dvp,")
523 (sleaf "options" "compose:caps,grp:win_space_toggle")
524 ])
525 ])
526
527 (flag "workspace-auto-back-and-forth")
528 # (sleaf "focus-follows-mouse" {})
529 # (flag "warp-mouse-to-focus")
530
531 # (plain "touchpad" [ (flag "off") ])
532 (plain "trackball" [
533 (sleaf "scroll-method" "on-button-down")
534 (sleaf "scroll-button" 278)
535 ])
536 (plain "touch" [
537 (sleaf "map-to-output" "eDP-1")
538 ])
539 ])
540
541 (plain "gestures" [
542 (plain "hot-corners" [(flag "off")])
543 ])
544
545 (plain "environment" (lib.mapAttrsToList sleaf {
546 NIXOS_OZONE_WL = "1";
547 QT_QPA_PLATFORM = "wayland";
548 QT_WAYLAND_DISABLE_WINDOWDECORATION = "1";
549 GDK_BACKEND = "wayland";
550 SDL_VIDEODRIVER = "wayland";
551 DISPLAY = ":0";
552 ELECTRON_OZONE_PLATFORM_HINT = "auto";
553 SSH_ASKPASS_REQUIRE = "prefer";
554 SSH_ASKPASS = lib.getExe pkgs.kdePackages.ksshaskpass;
555 SUDO_ASKPASS = lib.getExe pkgs.kdePackages.ksshaskpass;
556 }))
557
558 (node "output" ["eDP-1"] [
559 (sleaf "scale" 1.5)
560 (sleaf "position" { x = 0; y = 0; })
561 ])
562 (node "output" ["Ancor Communications Inc ASUS PB287Q 0x0000DD9B"] [
563 (sleaf "scale" 1.5)
564 (sleaf "position" { x = 2560; y = 0; })
565 ])
566 (node "output" ["HP Inc. HP 727pu CN4417143K"] [
567 (sleaf "mode" "2560x1440@119.998")
568 (sleaf "scale" 1)
569 (sleaf "position" { x = 2560; y = 0; })
570 (flag "variable-refresh-rate")
571 ])
572
573 (plain "debug" [
574 (sleaf "render-drm-device" "/dev/dri/by-path/pci-0000:00:02.0-render")
575 ])
576
577 (plain "animations" [
578 (sleaf "slowdown" 0.5)
579 (plain "workspace-switch" [(flag "off")])
580 ])
581
582 (plain "layout" [
583 (sleaf "gaps" 8)
584 (plain "struts" [
585 (sleaf "left" 26)
586 (sleaf "right" 26)
587 (sleaf "top" 0)
588 (sleaf "bottom" 0)
589 ])
590 (plain "border" [
591 (sleaf "width" 2)
592 (sleaf "active-gradient" {
593 from = "hsla(195 100% 45% 1)";
594 to = "hsla(155 100% 37.5% 1)";
595 angle = 29;
596 relative-to = "workspace-view";
597 })
598 (sleaf "inactive-gradient" {
599 from = "hsla(0 0% 27.7% 1)";
600 to = "hsla(0 0% 23% 1)";
601 angle = 29;
602 relative-to = "workspace-view";
603 })
604 ])
605 (plain "focus-ring" [
606 (flag "off")
607 ])
608
609 (plain "preset-column-widths" (map (prop: sleaf "proportion" prop) [
610 (1. / 4.) (1. / 3.) (1. / 2.) (2. / 3.) (3. / 4.) (1.)
611 ]))
612 (plain "default-column-width" [ (sleaf "proportion" (1. / 2.)) ])
613 (plain "preset-window-heights" (map (prop: sleaf "proportion" prop) [
614 (1. / 3.) (1. / 2.) (2. / 3.) (1.)
615 ]))
616
617 (flag "always-center-single-column")
618
619 (plain "tab-indicator" [
620 (sleaf "gap" 4)
621 (sleaf "width" 8)
622 (sleaf "gaps-between-tabs" 4)
623 (flag "place-within-column")
624 (sleaf "length" { total-proportion = 1.; })
625 (sleaf "active-gradient" {
626 from = "hsla(195 100% 60% 0.75)";
627 to = "hsla(155 100% 50% 0.75)";
628 angle = 29;
629 relative-to = "workspace-view";
630 })
631 (sleaf "inactive-gradient" {
632 from = "hsla(0 0% 42% 0.66)";
633 to = "hsla(0 0% 35% 0.66)";
634 angle = 29;
635 relative-to = "workspace-view";
636 })
637 ])
638 ])
639
640 (plain "cursor" [
641 (flag "hide-when-typing")
642 ])
643
644 (map (name:
645 (node "workspace" [name] [
646 (sleaf "open-on-output" "eDP-1")
647 ])
648 ) (map ({name, ...}: name) cfg.scratchspaces))
649 (map (name:
650 (sleaf "workspace" name)
651 ) ["comm" "web" "vid" "bmr"])
652
653 (plain "window-rule" [
654 (sleaf "clip-to-geometry" true)
655 ])
656
657 (plain "window-rule" [
658 (sleaf "match" { is-floating = true; })
659 (sleaf "geometry-corner-radius" 8)
660 (plain "shadow" [ (flag "on") ])
661 ])
662
663 (plain "window-rule" [
664 (sleaf "match" { app-id = "^org\\.keepassxc\\.KeePassXC$"; })
665 (sleaf "block-out-from" "screencast")
666 ])
667 (plain "window-rule" (normalize-nodes [
668 (map (title:
669 (sleaf "match" { app-id = "^org\\.keepassxc\\.KeePassXC$"; inherit title; })
670 ) ["^Unlock Database.*" "^Access Request.*" ".*Passkey credentials$" "Browser Access Request$"])
671 (sleaf "open-focused" true)
672 (sleaf "open-floating" true)
673 ]))
674
675 (map ({ name, match, exclude, windowRuleExtra, ... }:
676 (optional-node (match != []) (plain "window-rule" (normalize-nodes [
677 (map (sleaf "match") match)
678 (map (sleaf "exclude") exclude)
679 (sleaf "open-on-workspace" name)
680 (sleaf "open-maximized" true)
681 windowRuleExtra
682 ])))
683 ) cfg.scratchspaces)
684
685 (plain "window-rule" [
686 (sleaf "match" { app-id = "^emacs$"; })
687 (sleaf "match" { app-id = "^firefox$"; })
688 (plain "default-column-width" [(sleaf "proportion" (2. / 3.))])
689 ])
690 (plain "window-rule" [
691 (sleaf "match" { app-id = "^kitty$"; })
692 (sleaf "match" { app-id = "^kitty-play$"; })
693 (plain "default-column-width" [(sleaf "proportion" (1. / 3.))])
694 ])
695
696 (plain "window-rule" [
697 (sleaf "match" { app-id = "^thunderbird$"; })
698 (sleaf "match" { app-id = "^Element$"; })
699 (sleaf "match" { app-id = "^chrome-web\.openrainbow\.com__-Default$"; })
700 (sleaf "open-on-workspace" "comm")
701 ])
702 (plain "window-rule" [
703 (sleaf "match" { app-id = "^firefox$"; })
704 (sleaf "open-on-workspace" "web")
705 (sleaf "open-maximized" true)
706 ])
707 (plain "window-rule" [
708 (sleaf "match" { app-id = "^mpv$"; })
709 (sleaf "open-on-workspace" "vid")
710 (plain "default-column-width" [(sleaf "proportion" 1.)])
711 ])
712 (plain "window-rule" [
713 (sleaf "match" { app-id = "^kitty-play$"; })
714 (sleaf "open-on-workspace" "vid")
715 (sleaf "open-focused" false)
716 ])
717 (plain "window-rule" [
718 (sleaf "match" { app-id = "^chrome-audiobookshelf\.yggdrasil\.li__-Default$"; })
719 (sleaf "match" { app-id = "^YouTube Music Desktop App$"; })
720 (sleaf "open-on-workspace" "vid")
721 ])
722 (plain "window-rule" [
723 (sleaf "match" { app-id = "^pdfpc$"; })
724 (plain "default-column-width" [(sleaf "proportion" 1.)])
725 ])
726 (plain "window-rule" [
727 (sleaf "match" { app-id = "^pdfpc$"; title = "^.*presentation.*$"; })
728 (plain "default-column-width" [(sleaf "proportion" 1.)])
729 (sleaf "open-fullscreen" true)
730 (sleaf "open-on-workspace" "bmr")
731 (sleaf "open-focused" false)
732 ])
733 (plain "window-rule" (normalize-nodes [
734 (map (sleaf "match") [
735 { app-id = "^Gimp-"; title = "^Quit GIMP$"; }
736 { app-id = "^org\\.kde\\.polkit-kde-authentication-agent-1$"; }
737 { app-id = "^xdg-desktop-portal-gtk$"; }
738 ])
739 (sleaf "open-floating" true)
740 ]))
741 (plain "window-rule" [
742 (sleaf "match" { app-id = "^org\\.pwmt\\.zathura$"; })
743 (sleaf "match" { app-id = "^evince$"; })
744 (sleaf "match" { app-id = "^org\\.gnome\\.Papers$"; })
745 (sleaf "default-column-display" "tabbed")
746 ])
747
748 (plain "layer-rule" [
749 (sleaf "match" { namespace = "^notifications$"; })
750 (sleaf "match" { namespace = "^waybar$"; })
751 (sleaf "match" { namespace = "^launcher$"; })
752 (sleaf "block-out-from" "screencast")
753 ])
754
755 (plain "binds"
756 (let
757 bind = name: cfg: node name [(lib.removeAttrs cfg ["action"])] (lib.mapAttrsToList leaf (lib.removeAttrs cfg.action ["__functor"]));
758 in
759 normalize-nodes [
760 (lib.mapAttrsToList bind (with config.lib.niri.actions; {
761 "Mod+Slash".action = show-hotkey-overlay;
762
763 "Mod+Return".action = spawn terminal;
764 "Mod+Shift+Return".action =
765 let
766 nushellKitty = pkgs.symlinkJoin {
767 name = "nushell-kitty";
768 paths = [ config.programs.kitty.package ];
769 buildInputs = [ pkgs.makeWrapper ];
770 postBuild = ''
771 wrapProgram $out/bin/kitty \
772 --add-flags "--config ${pkgs.writeText "kitty.conf" ''
773 include $HOME/${config.xdg.configFile."kitty/kitty.conf".target}
774 shell ${lib.getExe config.programs.nushell.package}
775 ''}"
776 '';
777 };
778 in spawn (lib.getExe' nushellKitty "kitty");
779 "Mod+Q".action = close-window;
780 "Mod+O".action = spawn (lib.getExe config.programs.fuzzel.package);
781 "Mod+Shift+O".action = spawn (lib.getExe config.programs.fuzzel.package) "--list-executables-in-path";
782
783 "Mod+Alt+E".action = spawn (lib.getExe' config.services.emacs.package "emacsclient") "-c";
784 "Mod+Alt+Y".action = spawn (lib.getExe (pkgs.writeShellApplication {
785 name = "queue-yt-dlp";
786 runtimeInputs = with pkgs; [ wl-clipboard-rs socat ];
787 text = ''
788 socat STDIO UNIX-CONNECT:"$XDG_RUNTIME_DIR"/yt-dlp.sock <<<$'{ "urls": ["'"$(wl-paste)"$'"] }'
789 '';
790 }));
791 "Mod+Alt+L".action = spawn (lib.getExe (pkgs.writeShellApplication {
792 name = "queue-yt-dlp";
793 runtimeInputs = with pkgs; [ wl-clipboard-rs config.programs.kitty.package ];
794 text = ''
795 exec -- kitty --app-id kitty-play --directory "$HOME"/media mpv "$(wl-paste)"
796 '';
797 }));
798 "Mod+Alt+M".action = spawn (lib.getExe' pkgs.screen-message "sm") "-n" "Fira Mono" "-a" "1" "-f" "#fff" "-b" "#000";
799
800 "Mod+U".action = spawn (lib.getExe (pkgs.writeShellApplication {
801 name = "qalc-fuzzel";
802 runtimeInputs = with pkgs; [ wl-clipboard-rs libqalculate config.programs.fuzzel.package coreutils findutils libnotify gnugrep ];
803 text = ''
804 RESULTS_DIR="$HOME/.cache/qalc-fuzzel"
805 prev() {
806 FOUND=false
807 while IFS= read -r line; do
808 [[ -n "$line" ]] || continue
809 FOUND=true
810 echo "$line"
811 done < <(export LC_ALL=C.UTF-8; echo; find "$RESULTS_DIR" -type f -printf $'%T@ %p\n' | sort -n | cut -d' ' -f2- | xargs -r cat)
812 $FOUND || echo
813 }
814 FUZZEL_RES=$(prev | fuzzel --dmenu --prompt "qalc> " --width=60) || exit $?
815 if [[ "$FUZZEL_RES" =~ .*\ =\ .* ]]; then
816 QALC_RES="$FUZZEL_RES"
817 QALC_RET=0
818 else
819 QALC_RES=$(qalc -set "autocalc off" "$FUZZEL_RES" 2>&1)
820 QALC_RET=$?
821 fi
822 [[ -n "$QALC_RES" ]] || exit 1
823 EXISTING=false
824 set +o pipefail
825 grep -Fxrl "$QALC_RES" "$RESULTS_DIR" | xargs -r touch
826 [[ ''${PIPESTATUS[0]} -eq 0 ]] && EXISTING=true
827 set -o pipefail
828 if [[ $QALC_RET -eq 0 ]] && ! $EXISTING; then
829 set +o pipefail
830 RES_FILE="$RESULTS_DIR"/$(date -uIs).$(tr -Cd 'a-zA-Z0-9' </dev/random | head -c 10)
831 set -o pipefail
832 cat >"$RES_FILE" <<<"$QALC_RES"
833 fi
834 [[ "$QALC_RES" =~ .*\ =\ (.*) ]] && QALC_RES="''${BASH_REMATCH[1]}"
835 [[ $QALC_RET -eq 0 ]] && wl-copy "$QALC_RES"
836 notify-send "$QALC_RES"
837 '';
838 }));
839 "Mod+Shift+U".action =
840 let
841 qalcKitty = pkgs.symlinkJoin {
842 name = "qalc-kitty";
843 paths = [ config.programs.kitty.package ];
844 buildInputs = [ pkgs.makeWrapper ];
845 postBuild = ''
846 wrapProgram $out/bin/kitty \
847 --add-flags "--config ${pkgs.writeText "kitty.conf" ''
848 include $HOME/${config.xdg.configFile."kitty/kitty.conf".target}
849 shell ${lib.getExe pkgs.libqalculate}
850 ''}"
851 '';
852 };
853 in spawn (lib.getExe' qalcKitty "kitty");
854 "Mod+E".action = spawn (lib.getExe (pkgs.writeShellApplication {
855 name = "emoji-fuzzel";
856 runtimeInputs = with pkgs; [ config.programs.fuzzel.package wtype wl-clipboard-rs ];
857 text = ''
858 FUZZEL_RES=$(fuzzel --dmenu --prompt "emoji> " --cache "$HOME"/.cache/fuzzel-emoji --width=60 <"$HOME"/.local/share/emoji-data/list.txt) || exit $?
859 [[ -n "$FUZZEL_RES" ]] || exit 1
860 wl-copy "$(cut -d ':' -f 1 <<<"$FUZZEL_RES" | tr -d '\n')" && wtype -k XF86Paste
861 '';
862 }));
863 "Print".action = screenshot;
864 "Control+Print".action = screenshot-window;
865 "Shift+Print".action = kdl.magic-leaf "screenshot-screen";
866 "Mod+B".action = with-select-window-action ".workspace_id == ($active_workspace | tonumber)" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
867 "Mod+Shift+B".action = with-select-window-action "true" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
868
869 "Mod+Escape" = {
870 allow-inhibiting = false;
871 action = toggle-keyboard-shortcuts-inhibit;
872 };
873
874 "Mod+H".action = focus-column-left;
875 "Mod+T".action = focus-window-down;
876 "Mod+N".action = focus-window-up;
877 "Mod+S".action = focus-column-right;
878
879 "Mod+Shift+H".action = move-column-left;
880 "Mod+Shift+T".action = move-window-down;
881 "Mod+Shift+N".action = move-window-up;
882 "Mod+Shift+S".action = move-column-right;
883
884 "Mod+Control+H".action = focus-monitor-left;
885 "Mod+Control+T".action = focus-monitor-down;
886 "Mod+Control+N".action = focus-monitor-up;
887 "Mod+Control+S".action = focus-monitor-right;
888
889 "Mod+Shift+Control+H".action = move-workspace-to-monitor-left;
890 "Mod+Shift+Control+T".action = move-workspace-to-monitor-down;
891 "Mod+Shift+Control+N".action = move-workspace-to-monitor-up;
892 "Mod+Shift+Control+S".action = move-workspace-to-monitor-right;
893
894 "Mod+G".action = focus-adjacent-workspace "down";
895 "Mod+C".action = focus-adjacent-workspace "up";
896
897 "Mod+Shift+G".action = move-column-to-adjacent-workspace "down";
898 "Mod+Shift+C".action = move-column-to-adjacent-workspace "up";
899
900 "Mod+Shift+Control+G".action = move-workspace-down;
901 "Mod+Shift+Control+C".action = move-workspace-up;
902
903 "Mod+ParenLeft".action = focus-workspace "comm";
904 "Mod+Shift+ParenLeft".action = kdl.magic-leaf "move-column-to-workspace" "comm";
905
906 "Mod+ParenRight".action = focus-workspace "web";
907 "Mod+Shift+ParenRight".action = kdl.magic-leaf "move-column-to-workspace" "web";
908
909 "Mod+BraceRight".action = focus-workspace "read";
910 "Mod+Shift+BraceRight".action = kdl.magic-leaf "move-column-to-workspace" "read";
911
912 "Mod+BraceLeft".action = focus-workspace "mon";
913 "Mod+Shift+BraceLeft".action = kdl.magic-leaf "move-column-to-workspace" "mon";
914
915 "Mod+Asterisk".action = focus-workspace "vid";
916 "Mod+Shift+Asterisk".action = kdl.magic-leaf "move-column-to-workspace" "vid";
917
918 "Mod+Plus".action = with-unnamed-workspace-action ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
919 "Mod+Shift+Plus".action = with-unnamed-workspace-action ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}, "focus": true}}}'';
920
921 "Mod+M".action = consume-or-expel-window-left;
922 "Mod+W".action = consume-or-expel-window-right;
923
924 "Mod+Shift+M".action = toggle-column-tabbed-display;
925
926 "Mod+R".action = switch-preset-column-width;
927 "Mod+Shift+R".action = maximize-column;
928 "Mod+Shift+Ctrl+R".action = switch-preset-window-height;
929 "Mod+F".action = center-column;
930 "Mod+Shift+F".action = toggle-windowed-fullscreen;
931 "Mod+Ctrl+Shift+F".action = fullscreen-window;
932
933 "Mod+V".action = switch-focus-between-floating-and-tiling;
934 "Mod+Shift+V".action = toggle-window-floating;
935
936 "Mod+Left".action = set-column-width "-10%";
937 "Mod+Down".action = set-window-height "-10%";
938 "Mod+Up".action = set-window-height "+10%";
939 "Mod+Right".action = set-column-width "+10%";
940
941 "Mod+Shift+Z" = {
942 action = spawn (lib.getExe niri) "msg" "action" "power-off-monitors";
943 allow-when-locked = true;
944 };
945 "Mod+Shift+L".action = spawn loginctl "lock-session";
946 "Mod+Shift+E".action = quit;
947 "Mod+Shift+Minus" = {
948 action = spawn systemctl "suspend";
949 allow-when-locked = true;
950 };
951 "Mod+Shift+Control+Minus" = {
952 action = spawn systemctl "hibernate";
953 allow-when-locked = true;
954 };
955 "Mod+Shift+P" = {
956 action = spawn (lib.getExe pkgs.playerctl) "-a" "pause";
957 allow-when-locked = true;
958 };
959
960 "XF86MonBrightnessUp" = {
961 action = spawn swayosd-client "--brightness" "raise";
962 allow-when-locked = true;
963 };
964 "XF86MonBrightnessDown" = {
965 action = spawn swayosd-client "--brightness" "lower";
966 allow-when-locked = true;
967 };
968 "XF86AudioRaiseVolume" = {
969 action = spawn swayosd-client "--output-volume" "raise";
970 allow-when-locked = true;
971 };
972 "XF86AudioLowerVolume" = {
973 action = spawn swayosd-client "--output-volume" "lower";
974 allow-when-locked = true;
975 };
976 "XF86AudioMute" = {
977 action = spawn swayosd-client "--output-volume" "mute-toggle";
978 allow-when-locked = true;
979 };
980 "XF86AudioMicMute" = {
981 action = spawn swayosd-client "--input-volume" "mute-toggle";
982 allow-when-locked = true;
983 };
984
985 "Mod+Semicolon".action = spawn makoctl "dismiss" "--group";
986 "Mod+Shift+Semicolon".action = spawn makoctl "dismiss" "--all";
987 "Mod+Period".action = spawn makoctl "menu" "--" (lib.getExe config.programs.fuzzel.package) "--dmenu";
988 "Mod+Comma".action = spawn makoctl "restore";
989
990 "Mod+Control+W".action = with-empty-unnamed-workspace-action "{\"Action\":{\"FocusWorkspace\":{\"reference\":{\"Id\": $workspace_id}}}}";
991 "Mod+Control+Shift+W".action = with-empty-unnamed-workspace-action "{\"Action\":{\"MoveColumnToWorkspace\":{\"reference\":{\"Id\": $workspace_id}, \"focus\": true}}}";
992
993 "Mod+X".action = set-dynamic-cast-window;
994 "Mod+Shift+X".action = set-dynamic-cast-monitor;
995 "Mod+Control+Shift+X".action = clear-dynamic-cast-target;
996
997 "Mod+D".action = with-urgent-window-action "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
998 "Mod+Shift+D".action = with-focused-window-action "{\"Action\":{\"UnsetUrgent\":{\"id\": .id}}}";
999
1000 "Mod+K".action = spawn (lib.getExe' pkgs.worktime "worktime-ui");
1001 "Mod+Shift+K".action = spawn (lib.getExe' pkgs.worktime "worktime-stop");
1002 }))
1003 (map ({ name, selector, spawn, key, ...}: if key != null && selector != null && spawn != null then bind key { action = focus-or-spawn-action selector name spawn; } else null) cfg.scratchspaces)
1004 (map ({ name, moveKey, ...}: if moveKey != null then bind moveKey { action = kdl.magic-leaf "move-column-to-workspace" name; } else null) cfg.scratchspaces)
1005 ]
1006 ))
1007 ];
609 }; 1008 };
610} 1009}
diff --git a/accounts/gkleen@sif/niri/mako.nix b/accounts/gkleen@sif/niri/mako.nix
index 8fbc81c1..eba26caa 100644
--- a/accounts/gkleen@sif/niri/mako.nix
+++ b/accounts/gkleen@sif/niri/mako.nix
@@ -3,33 +3,39 @@
3 config = { 3 config = {
4 services.mako = { 4 services.mako = {
5 enable = true; 5 enable = true;
6 font = "Fira Sans 10"; 6 settings = {
7 format = "<i>%s</i>\\n%b"; 7 font = "Fira Sans 10";
8 margin = "2"; 8 format = "<i>%s</i>\\n%b";
9 maxVisible = -1; 9 margin = "2";
10 backgroundColor = "#000000dd"; 10 max-visible = -1;
11 progressColor = "source #223544ff"; 11 background-color = "#000000dd";
12 width = 384; 12 progress-color = "source #223544ff";
13 extraConfig = '' 13 width = 384;
14 outer-margin=1 14 outer-margin = 1;
15 max-history=100 15 max-history = 100;
16 max-icon-size=48 16 max-icon-size = 48;
17 17 };
18 [grouped] 18 criteria = {
19 format=<b>(%g)</b> <i>%s</i>\n%b 19 grouped.format = "<b>(%g)</b> <i>%s</i>\\n%b";
20 20 "urgency=low".text-color = "#999999ff";
21 [urgency=low] 21 "urgency=critical".background-color = "#900000dd";
22 text-color=#999999ff 22 "app-name=Element".group-by = "summary";
23 23 "app-name=poweralertd" = {
24 [urgency=critical] 24 history = false;
25 background-color=#900000dd 25 ignore-timeout = true;
26 26 default-timeout = 2000;
27 [app-name=Element] 27 };
28 group-by=summary 28 "app-name=worktime".history = false;
29 29 "mode=silent".invisible = true;
30 [mode=silent] 30 };
31 invisible=1 31 package = pkgs.symlinkJoin {
32 ''; 32 name = "${pkgs.mako.name}-wrapped";
33 paths = with pkgs; [ mako ];
34 inherit (pkgs.mako) meta;
35 postBuild = ''
36 rm -r $out/share/dbus-1
37 '';
38 };
33 }; 39 };
34 systemd.user.services.mako = { 40 systemd.user.services.mako = {
35 Unit = { 41 Unit = {
diff --git a/accounts/gkleen@sif/niri/swayosd.nix b/accounts/gkleen@sif/niri/swayosd.nix
new file mode 100644
index 00000000..54ebb302
--- /dev/null
+++ b/accounts/gkleen@sif/niri/swayosd.nix
@@ -0,0 +1,66 @@
1{ pkgs, ... }:
2{
3 config = {
4 services.swayosd = {
5 enable = true;
6 topMargin = 0.4769706078;
7 stylePath = pkgs.runCommand "style.css" {
8 passAsFile = [ "src" ];
9 src = ''
10 window#osd {
11 padding: 12px 20px;
12 border-radius: 999px;
13 border: none;
14 background: rgba(0, 0, 0, 0.87);
15
16 #container {
17 margin: 16px;
18 }
19
20 image,
21 label {
22 color: rgb(255, 255, 255);
23
24 &:disabled {
25 opacity: 1;
26 color: rgb(84, 84, 84);
27 }
28 }
29
30 progressbar {
31 min-height: 6px;
32 border-radius: 999px;
33 background: transparent;
34 border: none;
35
36 trough, progress {
37 min-height: inherit;
38 border-radius: inherit;
39 border: none;
40 }
41
42 trough {
43 background: rgb(127, 127, 127);
44 }
45 progress {
46 background: rgb(255, 255, 255);
47 }
48
49 &:disabled {
50 opacity: 1;
51
52 trough {
53 background: rgb(19, 19, 19);
54 }
55 progress {
56 background: rgb(38, 38, 38);
57 }
58 }
59 }
60 }
61 '';
62 buildInputs = with pkgs; [sass];
63 } "scss -C --sourcemap=none --style=compact $srcPath $out";
64 };
65 };
66}
diff --git a/accounts/gkleen@sif/niri/waybar.nix b/accounts/gkleen@sif/niri/waybar.nix
index 79c429f8..c02a9a76 100644
--- a/accounts/gkleen@sif/niri/waybar.nix
+++ b/accounts/gkleen@sif/niri/waybar.nix
@@ -1,5 +1,7 @@
1{ lib, pkgs, ... }: 1{ lib, config, pkgs, ... }:
2{ 2let
3 swayosd-client = lib.getExe' config.services.swayosd.package "swayosd-client";
4in {
3 config = { 5 config = {
4 programs.waybar = { 6 programs.waybar = {
5 enable = true; 7 enable = true;
@@ -18,15 +20,21 @@
18 { 20 {
19 layer = "top"; 21 layer = "top";
20 position = "top"; 22 position = "top";
21 height = 14; 23 height = 21;
22 output = [ "eDP-1" "DP-2" "DP-3" ]; 24 output = [ "eDP-1" "DP-2" "DP-3" ];
23 modules-left = [ "niri/workspaces" ]; 25 modules-left = [ "niri/workspaces" ];
24 modules-center = [ "niri/window" ]; 26 modules-center = [ "niri/window" ];
25 modules-right = [ # "custom/worktime" "custom/worktime-today" 27 modules-right = [ "custom/worktime" "custom/worktime-today"
26 "custom/weather" 28 "custom/weather"
27 "custom/keymap" 29 "custom/keymap"
28 "privacy" "tray" "wireplumber" "backlight" "battery" "idle_inhibitor" "custom/mako" "clock" ]; 30 "privacy" "tray" "wireplumber" "backlight" "battery" "idle_inhibitor" "custom/mako" "custom/lid_inhibitor" "clock" ];
29 31
32 "custom/lid_inhibitor" = {
33 format = "{}";
34 return-type = "json";
35 exec = lib.getExe pkgs.waybar-systemd-inhibit;
36 on-click = lib.getExe' pkgs.waybar-systemd-inhibit "waybar-systemd-inhibit-toggle";
37 };
30 "custom/mako" = { 38 "custom/mako" = {
31 format = "{}"; 39 format = "{}";
32 return-type = "json"; 40 return-type = "json";
@@ -59,7 +67,7 @@
59 text = f"<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>" # noqa: E501 67 text = f"<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>" # noqa: E501
60 if is_silent: 68 if is_silent:
61 text = f"<span color=\"#ffffff\">{text}</span>" 69 text = f"<span color=\"#ffffff\">{text}</span>"
62 print(json.dumps({'text': text}, separators=(',', ':')), flush=True) # noqa: E501 70 print(json.dumps({'text': text, 'tooltip': ', '.join(modes)}, separators=(',', ':')), flush=True) # noqa: E501
63 71
64 async def on_properties_changed(interface_name, changed_properties, invalidated_properties): # noqa: E501 72 async def on_properties_changed(interface_name, changed_properties, invalidated_properties): # noqa: E501
65 if "Modes" not in invalidated_properties: 73 if "Modes" not in invalidated_properties:
@@ -120,16 +128,16 @@
120 }; 128 };
121 "custom/worktime" = { 129 "custom/worktime" = {
122 interval = 60; 130 interval = 60;
123 exec = lib.getExe pkgs.worktime; 131 exec = "${lib.getExe pkgs.worktime} time --waybar";
124 tooltip = false; 132 return-type = "json";
125 }; 133 };
126 "custom/worktime-today" = { 134 "custom/worktime-today" = {
127 interval = 60; 135 interval = 60;
128 exec = "${lib.getExe pkgs.worktime} today"; 136 exec = "${lib.getExe pkgs.worktime} today --waybar";
129 tooltip = false; 137 return-type = "json";
130 }; 138 };
131 "niri/workspaces" = { 139 "niri/workspaces" = {
132 ignore = ["pwctl" "kpxc" "bmgr" "edit" "term"]; 140 ignore = map ({ name, ... }: name) config.programs.niri.scratchspaces;
133 }; 141 };
134 "niri/window" = { 142 "niri/window" = {
135 separate-outputs = true; 143 separate-outputs = true;
@@ -190,8 +198,8 @@
190 icon-size = iconSize; 198 icon-size = iconSize;
191 tooltip-format = "{percent}%"; 199 tooltip-format = "{percent}%";
192 format-icons = ["&#xf00da;" "&#xf00db;" "&#xf00dc;" "&#xf00dd;" "&#xf00de;" "&#xf00df;" "&#xf00e0;"]; 200 format-icons = ["&#xf00da;" "&#xf00db;" "&#xf00dc;" "&#xf00dd;" "&#xf00de;" "&#xf00df;" "&#xf00e0;"];
193 on-scroll-up = "lightctl -d -e4 -n1 up"; 201 on-scroll-up = "${swayosd-client} --brightness raise";
194 on-scroll-down = "lightctl -d -e4 -n1 down"; 202 on-scroll-down = "${swayosd-client} --brightness lower";
195 }; 203 };
196 wireplumber = { 204 wireplumber = {
197 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>"; 205 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>";
@@ -200,22 +208,22 @@
200 format-icons = ["&#xf057f;" "&#xf0580;" "&#xf057e;"]; 208 format-icons = ["&#xf057f;" "&#xf0580;" "&#xf057e;"];
201 format-muted = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">&#xf075f;</span>"; 209 format-muted = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">&#xf075f;</span>";
202 # ignored-sinks = ["Easy Effects Sink"]; 210 # ignored-sinks = ["Easy Effects Sink"];
203 on-scroll-up = "volumectl -d -u up"; 211 on-scroll-up = "${swayosd-client} --output-volume raise";
204 on-scroll-down = "volumectl -d -u down"; 212 on-scroll-down = "${swayosd-client} --output-volume lower";
205 on-click = "volumectl -d toggle-mute"; 213 on-click = "${swayosd-client} --output-volume mute-toggle";
206 }; 214 };
207 } 215 }
208 { 216 {
209 layer = "top"; 217 layer = "top";
210 position = "top"; 218 position = "top";
211 height = 14; 219 height = 14;
212 output = [ "!eDP-1" "!DP-2" "!DP-3" ]; 220 output = [ "!eDP-1" "!DP-2" "!DP-3" "*" ];
213 modules-left = [ "niri/workspaces" ]; 221 modules-left = [ "niri/workspaces" ];
214 modules-center = [ "niri/window" ]; 222 modules-center = [ "niri/window" ];
215 modules-right = [ "clock" ]; 223 modules-right = [ "clock" ];
216 224
217 "niri/workspaces" = { 225 "niri/workspaces" = {
218 ignore = ["pwctl" "kpxc" "bmgr" "edit" "term"]; 226 ignore = map ({ name, ... }: name) config.programs.niri.scratchspaces;
219 }; 227 };
220 "niri/window" = { 228 "niri/window" = {
221 separate-outputs = true; 229 separate-outputs = true;
@@ -241,7 +249,7 @@
241 249
242 * { 250 * {
243 border: none; 251 border: none;
244 font-family: "Fira Sans Nerd Font"; 252 font-family: "Fira Sans";
245 font-size: 10pt; 253 font-size: 10pt;
246 min-height: 0; 254 min-height: 0;
247 } 255 }
@@ -252,10 +260,10 @@
252 } 260 }
253 261
254 .modules-left { 262 .modules-left {
255 margin-left: 8px; 263 margin-left: 38px;
256 } 264 }
257 .modules-right { 265 .modules-right {
258 margin-right: 8px; 266 margin-right: 38px;
259 } 267 }
260 268
261 .module { 269 .module {
@@ -280,17 +288,18 @@
280 color: @grey; 288 color: @grey;
281 margin: 0 5px; 289 margin: 0 5px;
282 } 290 }
283 #custom-weather, #custom-worktime-today { 291 #custom-weather {
284 margin-right: 3px; 292 margin-right: 3px;
285 } 293 }
286 #custom-keymap, #custom-weather { 294 #custom-keymap {
287 margin-left: 3px; 295 margin-left: 3px;
296 margin-right: 3px;
288 } 297 }
289 298
290 #tray { 299 #tray {
291 margin: 0; 300 margin: 0;
292 } 301 }
293 #battery, #idle_inhibitor, #backlight, #wireplumber, #custom-mako { 302 #battery, #idle_inhibitor, #backlight, #wireplumber, #custom-mako, #custom-lid_inhibitor {
294 color: @grey; 303 color: @grey;
295 margin: 0 5px 0 2px; 304 margin: 0 5px 0 2px;
296 } 305 }
@@ -299,7 +308,11 @@
299 margin-left: 6px; 308 margin-left: 6px;
300 } 309 }
301 #custom-mako { 310 #custom-mako {
302 margin-right: 2px; 311 margin-right: 4px;
312 margin-left: 3px;
313 }
314 #custom-lid_inhibitor {
315 margin-right: 3px;
303 margin-left: 3px; 316 margin-left: 3px;
304 } 317 }
305 #battery { 318 #battery {
@@ -320,17 +333,24 @@
320 #idle_inhibitor.activated { 333 #idle_inhibitor.activated {
321 color: @white; 334 color: @white;
322 } 335 }
336 #custom-worktime.running, #custom-worktime-today.running {
337 color: @white;
338 }
339 #custom-worktime.over, #custom-worktime-today.over {
340 color: @orange;
341 }
323 342
324 #idle_inhibitor { 343 #idle_inhibitor, #custom-lid_inhibitor {
325 padding-top: 1px; 344 padding-top: 1px;
326 } 345 }
327 346
328 #privacy { 347 #privacy {
329 color: @red; 348 color: @red;
330 margin: -1px 2px 0px 5px; 349 margin: -1px 4px 0px 3px;
331 } 350 }
332 #clock { 351 #clock {
333 /* margin-right: 5px; */ 352 /* margin-right: 5px; */
353 font-feature-settings: "tnum";
334 } 354 }
335 ''; 355 '';
336 }; 356 };
diff --git a/accounts/gkleen@sif/ssh-hosts.nix b/accounts/gkleen@sif/ssh-hosts.nix
index 107f1e76..a250509b 100644
--- a/accounts/gkleen@sif/ssh-hosts.nix
+++ b/accounts/gkleen@sif/ssh-hosts.nix
@@ -1,5 +1,12 @@
1{ pkgs, ... }: 1{ lib, pkgs, ... }:
2{ 2let
3 autosshProxyPorts = {
4 "ssh.math.lmu.de" = 8118;
5 "mathw0h" = 8122;
6 "mathw0e" = 8124;
7 };
8 autosshProxy = host: "${lib.getExe pkgs.socat} - SOCKS4A:127.0.0.1:%h:%p,socksport=${toString autosshProxyPorts.${host}}";
9in {
3 "git.ymir" = 10 "git.ymir" =
4 { hostname = "ymir.yggdrasil.li"; 11 { hostname = "ymir.yggdrasil.li";
5 user = "gitolite"; 12 user = "gitolite";
@@ -290,15 +297,15 @@
290 }; 297 };
291 "mathw0d" = 298 "mathw0d" =
292 { hostname = "mathw0d.mathinst.loc"; 299 { hostname = "mathw0d.mathinst.loc";
293 proxyJump = "mathw0h"; 300 proxyCommand = autosshProxy "mathw0h";
294 }; 301 };
295 "mathw0e" = 302 "mathw0e" =
296 { hostname = "mathw0e.mathinst.loc"; 303 { hostname = "mathw0e.mathinst.loc";
297 proxyJump = "mathw0h"; 304 proxyCommand = autosshProxy "mathw0h";
298 }; 305 };
299 "mathw0f" = 306 "mathw0f" =
300 { hostname = "mathw0f.mathinst.loc"; 307 { hostname = "mathw0f.mathinst.loc";
301 proxyJump = "mathw0h"; 308 proxyCommand = autosshProxy "mathw0h";
302 }; 309 };
303 "mathw0g" = 310 "mathw0g" =
304 { hostname = "mathw0g.mathinst.loc"; 311 { hostname = "mathw0g.mathinst.loc";
@@ -306,8 +313,8 @@
306 "mathw0h" = 313 "mathw0h" =
307 { hostname = "mathw0h.mathinst.loc"; 314 { hostname = "mathw0h.mathinst.loc";
308 }; 315 };
309 "proxy.mathw0g" = 316 "proxy.ssh.math.lmu.de" =
310 { hostname = "mathw0g.mathinst.loc"; 317 { hostname = "ssh.math.lmu.de";
311 extraOptions = { 318 extraOptions = {
312 ControlPath = "none"; 319 ControlPath = "none";
313 ExitOnForwardFailure = "yes"; 320 ExitOnForwardFailure = "yes";
@@ -317,7 +324,17 @@
317 }; 324 };
318 "proxy.mathw0h" = 325 "proxy.mathw0h" =
319 { hostname = "mathw0h.mathinst.loc"; 326 { hostname = "mathw0h.mathinst.loc";
320 proxyJump = "proxy.mathw0g"; 327 proxyCommand = autosshProxy "ssh.math.lmu.de";
328 extraOptions = {
329 ControlPath = "none";
330 ExitOnForwardFailure = "yes";
331 ServerAliveCountMax = "15";
332 ServerAliveInterval = "2";
333 };
334 };
335 "proxy.mathw0e" =
336 { hostname = "mathw0e.mathinst.loc";
337 proxyCommand = autosshProxy "mathw0h";
321 extraOptions = { 338 extraOptions = {
322 ControlPath = "none"; 339 ControlPath = "none";
323 ExitOnForwardFailure = "yes"; 340 ExitOnForwardFailure = "yes";
@@ -327,7 +344,7 @@
327 }; 344 };
328 "vrt-kvm06" = 345 "vrt-kvm06" =
329 { hostname = "vrt-kvm06"; 346 { hostname = "vrt-kvm06";
330 proxyJump = "mathw0e"; 347 proxyCommand = autosshProxy "mathw0e";
331 user = "root"; 348 user = "root";
332 extraOptions = { 349 extraOptions = {
333 PasswordAuthentication = "yes"; 350 PasswordAuthentication = "yes";
@@ -336,7 +353,7 @@
336 }; 353 };
337 "vrt-kvm05" = 354 "vrt-kvm05" =
338 { hostname = "vrt-kvm05"; 355 { hostname = "vrt-kvm05";
339 proxyJump = "mathw0e"; 356 proxyCommand = autosshProxy "mathw0e";
340 user = "root"; 357 user = "root";
341 extraOptions = { 358 extraOptions = {
342 PasswordAuthentication = "yes"; 359 PasswordAuthentication = "yes";
@@ -345,7 +362,7 @@
345 }; 362 };
346 "vrt-kvm04" = 363 "vrt-kvm04" =
347 { hostname = "vrt-kvm04"; 364 { hostname = "vrt-kvm04";
348 proxyJump = "mathw0e"; 365 proxyCommand = autosshProxy "mathw0e";
349 user = "root"; 366 user = "root";
350 extraOptions = { 367 extraOptions = {
351 PasswordAuthentication = "yes"; 368 PasswordAuthentication = "yes";
@@ -354,7 +371,7 @@
354 }; 371 };
355 "vrt-kvm02" = 372 "vrt-kvm02" =
356 { hostname = "vrt-kvm02"; 373 { hostname = "vrt-kvm02";
357 proxyJump = "mathw0e"; 374 proxyCommand = autosshProxy "mathw0e";
358 user = "root"; 375 user = "root";
359 extraOptions = { 376 extraOptions = {
360 PasswordAuthentication = "yes"; 377 PasswordAuthentication = "yes";
@@ -363,7 +380,7 @@
363 }; 380 };
364 "vrt-kvm03" = 381 "vrt-kvm03" =
365 { hostname = "vrt-kvm03"; 382 { hostname = "vrt-kvm03";
366 proxyJump = "mathw0e"; 383 proxyCommand = autosshProxy "mathw0e";
367 user = "root"; 384 user = "root";
368 extraOptions = { 385 extraOptions = {
369 PasswordAuthentication = "yes"; 386 PasswordAuthentication = "yes";
@@ -372,7 +389,7 @@
372 }; 389 };
373 "vrt-kvm01" = 390 "vrt-kvm01" =
374 { hostname = "vrt-kvm01"; 391 { hostname = "vrt-kvm01";
375 proxyJump = "mathw0e"; 392 proxyCommand = autosshProxy "mathw0e";
376 user = "root"; 393 user = "root";
377 extraOptions = { 394 extraOptions = {
378 PasswordAuthentication = "yes"; 395 PasswordAuthentication = "yes";
@@ -381,39 +398,44 @@
381 }; 398 };
382 "tts-www01" = 399 "tts-www01" =
383 { hostname = "tts-www01.mathinst.loc"; 400 { hostname = "tts-www01.mathinst.loc";
384 proxyJump = "mathw0h"; 401 proxyCommand = autosshProxy "mathw0h";
385 user = "root"; 402 user = "root";
386 }; 403 };
387 "vpn-wg01" = 404 "vpn-wg01" =
388 { hostname = "vpn-wg01.mathinst.loc"; 405 { hostname = "vpn-wg01.mathinst.loc";
389 proxyJump = "mathw0h"; 406 proxyCommand = autosshProxy "mathw0h";
390 user = "root"; 407 user = "root";
391 }; 408 };
392 "repo-apt01" = 409 "repo-apt01" =
393 { hostname = "repo-apt01.mathinst.loc"; 410 { hostname = "repo-apt01.mathinst.loc";
394 proxyJump = "mathw0h"; 411 proxyCommand = autosshProxy "mathw0h";
395 user = "root"; 412 user = "root";
396 }; 413 };
397 "ldap-lmumr01" = 414 "ldap-lmumr01" =
398 { hostname = "ldap-lmumr01.mathinst.loc"; 415 { hostname = "ldap-lmumr01.mathinst.loc";
399 proxyJump = "mathw0h"; 416 proxyCommand = autosshProxy "mathw0h";
400 user = "root"; 417 user = "root";
401 }; 418 };
402 "mail-mi01" = 419 "mail-mi01" =
403 { hostname = "mail-mi01.mathinst.loc"; 420 { hostname = "mail-mi01.mathinst.loc";
404 proxyJump = "mathw0h"; 421 proxyCommand = autosshProxy "mathw0h";
405 }; 422 };
406 "mail-www02" = 423 "mail-www02" =
407 { hostname = "mail-www02.mathinst.loc"; 424 { hostname = "mail-www02.mathinst.loc";
408 proxyJump = "mathw0h"; 425 proxyCommand = autosshProxy "mathw0h";
409 }; 426 };
410 "dpl-fai01" = 427 "dpl-fai01" =
411 { hostname = "dpl-fai01.mathinst.loc"; 428 { hostname = "dpl-fai01.mathinst.loc";
412 user = "root"; 429 user = "root";
413 }; 430 };
431 "dpl-fai02" =
432 { hostname = "dpl-fai02.mathinst.loc";
433 user = "root";
434 proxyJump = "mgmt01";
435 };
414 "math05" = 436 "math05" =
415 { hostname = "math05.mathinst.loc"; 437 { hostname = "math05.mathinst.loc";
416 proxyJump = "mathw0h"; 438 proxyCommand = autosshProxy "mathw0h";
417 extraOptions.KexAlgorithms = "+diffie-hellman-group1-sha1"; 439 extraOptions.KexAlgorithms = "+diffie-hellman-group1-sha1";
418 }; 440 };
419 "switch01" = 441 "switch01" =
@@ -439,20 +461,20 @@
439 }; 461 };
440 "www-mi01" = 462 "www-mi01" =
441 { hostname = "www-mi01.mathinst.loc"; 463 { hostname = "www-mi01.mathinst.loc";
442 proxyJump = "mathw0h"; 464 proxyCommand = autosshProxy "mathw0h";
443 }; 465 };
444 "cip04" = 466 "cip04" =
445 { hostname = "cip04.cipmath.loc"; 467 { hostname = "cip04.cipmath.loc";
446 proxyJump = "mathw0h"; 468 proxyCommand = autosshProxy "mathw0h";
447 }; 469 };
448 "mgmt-cls01" = 470 "mgmt-cls01" =
449 { user = "root"; 471 { user = "root";
450 hostname = "mgmt-cls01.cipmath.loc"; 472 hostname = "mgmt-cls01.cipmath.loc";
451 proxyJump = "ssh.math.lmu.de"; 473 proxyCommand = autosshProxy "ssh.math.lmu.de";
452 }; 474 };
453 "mgmt01" = 475 "mgmt01" =
454 { hostname = "mgmt01.mathinst.loc"; 476 { hostname = "mgmt01.mathinst.loc";
455 proxyJump = "mathw0h"; 477 proxyCommand = autosshProxy "mathw0h";
456 user = "root"; 478 user = "root";
457 }; 479 };
458 "ssh-lb01" = 480 "ssh-lb01" =
@@ -471,17 +493,17 @@
471 "rdlx02" = { hostname = "rdlx02.mathinst.loc"; proxyJump = "mgmt01"; }; 493 "rdlx02" = { hostname = "rdlx02.mathinst.loc"; proxyJump = "mgmt01"; };
472 "math0d" = 494 "math0d" =
473 { hostname = "math0d.mathinst.loc"; 495 { hostname = "math0d.mathinst.loc";
474 proxyJump = "mathw0h"; 496 proxyCommand = autosshProxy "mathw0h";
475 }; 497 };
476 "dhcp01" = 498 "dhcp01" =
477 { hostname = "dhcp01.mathinst.loc"; 499 { hostname = "dhcp01.mathinst.loc";
478 user = "root"; 500 user = "root";
479 proxyJump = "mathw0h"; 501 proxyCommand = autosshProxy "mathw0h";
480 }; 502 };
481 "dhcp02" = 503 "dhcp02" =
482 { hostname = "dhcp02.mathinst.loc"; 504 { hostname = "dhcp02.mathinst.loc";
483 user = "root"; 505 user = "root";
484 proxyJump = "mathw0h"; 506 proxyCommand = autosshProxy "mathw0h";
485 }; 507 };
486 "cc-gpu-l01" = 508 "cc-gpu-l01" =
487 { hostname = "cc-gpu-l01.mathinst.loc"; 509 { hostname = "cc-gpu-l01.mathinst.loc";
@@ -546,7 +568,7 @@
546 user = "root"; 568 user = "root";
547 }; 569 };
548 "nas*" = 570 "nas*" =
549 { proxyJump = "mathw0e"; 571 { proxyCommand = autosshProxy "mathw0e";
550 user = "admin"; 572 user = "admin";
551 extraOptions = { 573 extraOptions = {
552 PasswordAuthentication = "yes"; 574 PasswordAuthentication = "yes";
@@ -554,9 +576,4 @@
554 HostKeyAlgorithms = "+ecdsa-sha2-nistp256"; 576 HostKeyAlgorithms = "+ecdsa-sha2-nistp256";
555 }; 577 };
556 }; 578 };
557 "game01" =
558 { hostname = "game01.yggdrasil.li";
559 user = "factorio";
560 identityFile = "~/.ssh/gkleen@sif.midgard.yggdrasil";
561 };
562} 579}
diff --git a/accounts/gkleen@sif/synadm/default.nix b/accounts/gkleen@sif/synadm/default.nix
new file mode 100644
index 00000000..0a8e0d4c
--- /dev/null
+++ b/accounts/gkleen@sif/synadm/default.nix
@@ -0,0 +1,9 @@
1{ config, pkgs, ... }:
2{
3 home.packages = with pkgs; [ synadm ];
4 sops.secrets."synadm.yaml" = {
5 format = "binary";
6 sopsFile = ./synadm_yaml;
7 path = config.xdg.configHome + "/synadm.yaml";
8 };
9}
diff --git a/accounts/gkleen@sif/synadm/synadm_yaml b/accounts/gkleen@sif/synadm/synadm_yaml
new file mode 100644
index 00000000..8d951ccc
--- /dev/null
+++ b/accounts/gkleen@sif/synadm/synadm_yaml
@@ -0,0 +1,15 @@
1{
2 "data": "ENC[AES256_GCM,data:qJy4Pmbbxja4jmW7OaHsD0mQZ7anZwLhiVmAgkavb+CqwWGDnUBXdz22/MHCbxng5NshcFSpBoCBhgY6B9V2bUiES6bH9AtMlDcs9ebKGMArBTUTnQ2MjWQGfQTqraWdNgy+n327uj9swwCH8EZXdYH/Hlv0t/re470W+VOHeXhGghQ3Y9IGz2sgfvMGr8QxaJNydZz85rgs5QUP/PglCwWIOw2mY1EX2vYwnmiAo49LmIEaxWvRi++KHaeBveDt0nlkJwzUlipL2VOKWxkgpK3yGucQn2mz+FRe1btp+4KGm8H17eUI9FO9sBwq,iv:kgM921ovwCgDYHQj3c5Rupy/8JxHehxUD2jb1k9Ik2Y=,tag:3TLQkJbv679VWy8V2TMugw==,type:str]",
3 "sops": {
4 "age": [
5 {
6 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
7 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6bzVHUGNxZTF2WC9MYmZr\neGdVVzJXN3lGdEk3cTBER3J6UTFtcUJna2d3CjdNQmRXd2haZW1MYlJzNkk1dWVD\nVTFQc2gvS0JrejJ6SFh2MXpPWDZpRE0KLS0tIE0wTC85bEpvSnlGdGFkZVFhNjFZ\nbzRiZkxMWUg2ODNVUlBmNFlPNGRrZlkK1VXLJWcssv3ETyZSSM/Hhn5VIaI9iov9\nzShZA9Zx/FX6PYTuUMC29pJ57gKourcIxa/7HwSv/xYn1A6WcYfgSg==\n-----END AGE ENCRYPTED FILE-----\n"
8 }
9 ],
10 "lastmodified": "2025-05-18T11:03:42Z",
11 "mac": "ENC[AES256_GCM,data:yonJC68PhilAgEHNNJQ8nO53Qo3rx/LnfiOWfuMm24bOUIH9QM3WZZxpigd7bHI4eC4TqRb4LvcSi0nEURTRAhwiTqGNrWbpw2Iv3n5dhLEN9aTcetG5ZuhaXqfVUoML45/ovdBZG/0l8+XIHqxN2M/g/h4JwKoR/6lqzcrVhgo=,iv:xvxBJwy+E5zUdjhGPdZPdy7tnBIEj50hfiDJFsS3wNg=,tag:L4Fas36ZOg4h0QQwC4gjNA==,type:str]",
12 "unencrypted_suffix": "_unencrypted",
13 "version": "3.10.2"
14 }
15}
diff --git a/accounts/gkleen@sif/systemd.nix b/accounts/gkleen@sif/systemd.nix
index d3a1a4c7..18c2315f 100644
--- a/accounts/gkleen@sif/systemd.nix
+++ b/accounts/gkleen@sif/systemd.nix
@@ -6,7 +6,7 @@ let
6 cfg = config.home-manager.users.${userName}; 6 cfg = config.home-manager.users.${userName};
7 7
8 autossh-socks-script = pkgs.writeScript "autossh" '' 8 autossh-socks-script = pkgs.writeScript "autossh" ''
9 #!${pkgs.zsh}/bin/zsh -xe 9 #!${lib.getExe pkgs.zsh} -xe
10 10
11 host="''${1%:*}" 11 host="''${1%:*}"
12 port="''${1#*:}" 12 port="''${1#*:}"
@@ -15,31 +15,29 @@ let
15 cmd=() 15 cmd=()
16 16
17 if [[ -n "''${SSHPASS_SECRET}" ]]; then 17 if [[ -n "''${SSHPASS_SECRET}" ]]; then
18 cmd+=(${pkgs.sshpassSecret}/bin/sshpass-secret) 18 cmd+=(${lib.getExe' pkgs.sshpassSecret "sshpass-secret"})
19 cmd+=("''${(@s/:/)SSHPASS_SECRET}") 19 cmd+=("''${(@s/:/)SSHPASS_SECRET}")
20 cmd+=(--) 20 cmd+=(--)
21 fi 21 fi
22 22
23 cmd+=(${pkgs.openssh}/bin/ssh -vN -D localhost:''${port} "''${host}") 23 cmd+=(${lib.getExe' pkgs.openssh "ssh"} -vN -D 127.0.0.1:''${port} "''${host}")
24 24
25 ( exec -a "''${cmd[1]}" -- ''${cmd} ) & 25 ( exec -a "''${cmd[1]}" -- ''${cmd} ) &
26 pid=$! 26 pid=$!
27 27
28 newpid="" 28 newpid=""
29 i=200 29 i=200
30 while ! newpid=$(${pkgs.lsof}/bin/lsof -Pi @localhost:"''${port}" -sTCP:LISTEN -t); do 30 while ! newpid=$(${lib.getExe pkgs.lsof} -Pi @localhost:"''${port}" -sTCP:LISTEN -t); do
31 if ! kill -0 "''${pid}"; then 31 if ! kill -0 "''${pid}"; then
32 wait "''${pid}" 32 wait "''${pid}"
33 exit $? 33 exit $?
34 fi 34 fi
35 [[ "''${i}" -gt 0 ]] || exit 1 35 [[ "''${i}" -gt 0 ]] || exit 1
36 i=$((''${i} - 1)) 36 i=$((''${i} - 1))
37 ${pkgs.coreutils}/bin/sleep 0.1 37 ${lib.getExe' pkgs.coreutils "sleep"} 0.1
38 done 38 done
39 39
40 ${config.systemd.package}/bin/systemd-notify --ready 40 ${lib.getExe' config.systemd.package "systemd-notify"} --pid=''${newpid} --ready
41
42 wait "''${pid}" "''${newpid}"
43 ''; 41 '';
44in { 42in {
45 tmpfiles.rules = [ 43 tmpfiles.rules = [
@@ -48,11 +46,11 @@ in {
48 ]; 46 ];
49 47
50 services = { 48 services = {
51 sync-keepass = { 49 "sync-keepass@" = {
52 Service = { 50 Service = {
53 Type = "oneshot"; 51 Type = "oneshot";
54 WorkingDirectory = "~"; 52 WorkingDirectory = "~";
55 ExecStart = toString (pkgs.writers.writePython3 "sync-keepass" { 53 ExecStart = "${pkgs.writers.writePython3 "sync-keepass" {
56 libraries = with pkgs.python3Packages; [ python-dateutil ]; 54 libraries = with pkgs.python3Packages; [ python-dateutil ];
57 } '' 55 } ''
58 import json 56 import json
@@ -61,13 +59,13 @@ in {
61 from datetime import datetime 59 from datetime import datetime
62 from dateutil.tz import tzlocal 60 from dateutil.tz import tzlocal
63 from dateutil.parser import isoparse 61 from dateutil.parser import isoparse
64 from sys import stderr 62 from sys import stderr, argv
65 63
66 64
67 remote_fs = 'surtr' 65 remote_fs = 'surtr' if argv[1] == 'store.kdbx' else 'mathcloud'
68 remote_file = 'store.kdbx' 66 remote_file = argv[1]
69 target_file = expanduser('~/store.kdbx') 67 target_file = expanduser(f'~/{argv[1]}')
70 meta_file = expanduser('~/.store.kdbx.json') 68 meta_file = expanduser(f'~/.{argv[1]}.json')
71 69
72 upload_time = None 70 upload_time = None
73 our_last_upload_time = None 71 our_last_upload_time = None
@@ -117,7 +115,7 @@ in {
117 do_upload() 115 do_upload()
118 elif upload_time is not None and (mod_time is None or upload_time > mod_time) and (our_last_upload_time is None or upload_time > our_last_upload_time): # noqa: E501 116 elif upload_time is not None and (mod_time is None or upload_time > mod_time) and (our_last_upload_time is None or upload_time > our_last_upload_time): # noqa: E501
119 do_download() 117 do_download()
120 ''); 118 ''} \"%I\"";
121 Environment = [ "RCLONE_PASSWORD_COMMAND=\"${pkgs.coreutils}/bin/cat ${config.sops.secrets.gkleen-rclone.path}\"" "PATH=${pkgs.rclone}/bin" ]; 119 Environment = [ "RCLONE_PASSWORD_COMMAND=\"${pkgs.coreutils}/bin/cat ${config.sops.secrets.gkleen-rclone.path}\"" "PATH=${pkgs.rclone}/bin" ];
122 }; 120 };
123 }; 121 };
@@ -146,7 +144,7 @@ in {
146 Service.ExecStart = "${pkgs.bluez}/bin/mpris-proxy"; 144 Service.ExecStart = "${pkgs.bluez}/bin/mpris-proxy";
147 Install.WantedBy = [ "default.target" ]; 145 Install.WantedBy = [ "default.target" ];
148 }; 146 };
149 "autossh-socks@proxy.mathw0h:8119" = { 147 "autossh-socks@proxy.ssh.math.lmu.de:8119" = {
150 Service = { 148 Service = {
151 Type = "notify"; 149 Type = "notify";
152 NotifyAccess = "all"; 150 NotifyAccess = "all";
@@ -154,7 +152,7 @@ in {
154 Restart = "always"; 152 Restart = "always";
155 RestartSec = "23s"; 153 RestartSec = "23s";
156 ExecStart = "${autossh-socks-script} \"%I\""; 154 ExecStart = "${autossh-socks-script} \"%I\"";
157 Environment = [ "SSHPASS_SECRET=gkleen@mathw0g.math.lmu.de" ]; 155 Environment = [ "SSHPASS_SECRET=gkleen@ssh.math.lmu.de" ];
158 }; 156 };
159 Unit = { 157 Unit = {
160 StopWhenUnneeded = true; 158 StopWhenUnneeded = true;
@@ -175,6 +173,38 @@ in {
175 StopWhenUnneeded = true; 173 StopWhenUnneeded = true;
176 }; 174 };
177 }; 175 };
176 "autossh-socks@proxy.mathw0h:8123" = {
177 Service = {
178 Type = "notify";
179 NotifyAccess = "all";
180 WorkingDirectory = "~";
181 Restart = "always";
182 RestartSec = "23s";
183 ExecStart = "${autossh-socks-script} \"%I\"";
184 Environment = [ "SSHPASS_SECRET=gkleen@mathw0h.mathinst.loc" ];
185 };
186 Unit = {
187 StopWhenUnneeded = true;
188 StartLimitInterval = "180s";
189 StartLimitBurst = 7;
190 };
191 };
192 "autossh-socks@proxy.mathw0e:8125" = {
193 Service = {
194 Type = "notify";
195 NotifyAccess = "all";
196 WorkingDirectory = "~";
197 Restart = "always";
198 RestartSec = "23s";
199 ExecStart = "${autossh-socks-script} \"%I\"";
200 Environment = [ "SSHPASS_SECRET=gkleen@mathw0e.mathinst.loc" ];
201 };
202 Unit = {
203 StopWhenUnneeded = true;
204 StartLimitInterval = "180s";
205 StartLimitBurst = 7;
206 };
207 };
178 swayidle = { 208 swayidle = {
179 Service = { 209 Service = {
180 RuntimeDirectory = "swayidle"; 210 RuntimeDirectory = "swayidle";
@@ -212,7 +242,7 @@ in {
212 "-${lib.getExe pkgs.playerctl} -a pause" 242 "-${lib.getExe pkgs.playerctl} -a pause"
213 "-${lib.getExe (pkgs.writeShellApplication { 243 "-${lib.getExe (pkgs.writeShellApplication {
214 name = "generate-css"; 244 name = "generate-css";
215 runtimeInputs = with pkgs; [cfg.programs.wpaperd.package jq coreutils imagemagick findutils]; 245 runtimeInputs = with pkgs; [cfg.services.wpaperd.package jq coreutils imagemagick findutils];
216 text = '' 246 text = ''
217 declare -A monitors 247 declare -A monitors
218 monitors=() 248 monitors=()
@@ -303,21 +333,21 @@ in {
303 ExecStopPost = "${pkgs.coreutils}/bin/rm -rfv \"$CACHE_DIRECTORY\""; 333 ExecStopPost = "${pkgs.coreutils}/bin/rm -rfv \"$CACHE_DIRECTORY\"";
304 }; 334 };
305 }; 335 };
306 wpaperd = { 336 # wpaperd = {
307 Install = { 337 # Install = {
308 WantedBy = ["graphical-session.target"]; 338 # WantedBy = ["graphical-session.target"];
309 }; 339 # };
310 Unit = { 340 # Unit = {
311 After = [ "graphical-session.target" ]; 341 # After = [ "graphical-session.target" ];
312 PartOf = [ "graphical-session.target" ]; 342 # PartOf = [ "graphical-session.target" ];
313 }; 343 # };
314 Service = { 344 # Service = {
315 ExecStart = lib.getExe cfg.programs.wpaperd.package; 345 # ExecStart = lib.getExe cfg.services.wpaperd.package;
316 Type = "simple"; 346 # Type = "simple";
317 Restart = "always"; 347 # Restart = "always";
318 RestartSec = "2s"; 348 # RestartSec = "2s";
319 }; 349 # };
320 }; 350 # };
321 xembed-sni-proxy = { 351 xembed-sni-proxy = {
322 Unit = { 352 Unit = {
323 PartOf = lib.mkForce ["tray.target"]; 353 PartOf = lib.mkForce ["tray.target"];
@@ -344,16 +374,21 @@ in {
344 Unit = { 374 Unit = {
345 PartOf = lib.mkForce ["tray.target"]; 375 PartOf = lib.mkForce ["tray.target"];
346 }; 376 };
377 Install = {
378 WantedBy = lib.mkForce ["tray.target"];
379 };
347 }; 380 };
348 } // listToAttrs (map ({host, port}: nameValuePair "proxy-to-autossh-socks@${toString port}" { 381 } // listToAttrs (map ({host, port}: nameValuePair "proxy-to-autossh-socks@${toString port}" {
349 Unit = { 382 Unit = {
350 Requires = ["autossh-socks@${host}:${toString (port + 1)}.service" "proxy-to-autossh-socks@${toString port}.socket"]; 383 BindsTo = ["autossh-socks@${host}:${toString (port + 1)}.service" "proxy-to-autossh-socks@${toString port}.socket"];
351 After = ["autossh-socks@${host}:${toString (port + 1)}.service" "proxy-to-autossh-socks@${toString port}.socket"]; 384 After = ["autossh-socks@${host}:${toString (port + 1)}.service" "proxy-to-autossh-socks@${toString port}.socket"];
352 }; 385 };
353 Service = { 386 Service = {
354 ExecStart = "${config.systemd.package}/lib/systemd/systemd-socket-proxyd --exit-idle-time=10s localhost:${toString (port + 1)}"; 387 ExecStart = "${config.systemd.package}/lib/systemd/systemd-socket-proxyd --exit-idle-time=60s 127.0.0.1:${toString (port + 1)}";
388 Restart = "always";
389 RestartSec = "23s";
355 }; 390 };
356 }) [{ host = "proxy.mathw0h"; port = 8118; } { host = "proxy.vidhar"; port = 8120; }]); 391 }) [{ host = "proxy.ssh.math.lmu.de"; port = 8118; } { host = "proxy.vidhar"; port = 8120; } { host = "proxy.mathw0h"; port = 8122; } { host = "proxy.mathw0e"; port = 8124; }]);
357 sockets = listToAttrs (map (port: nameValuePair "proxy-to-autossh-socks@${toString port}" { 392 sockets = listToAttrs (map (port: nameValuePair "proxy-to-autossh-socks@${toString port}" {
358 Socket = { 393 Socket = {
359 ListenStream = "%I"; 394 ListenStream = "%I";
@@ -361,7 +396,7 @@ in {
361 Install = { 396 Install = {
362 WantedBy = ["default.target"]; 397 WantedBy = ["default.target"];
363 }; 398 };
364 }) [8118 8120]) // { 399 }) [8118 8120 8122 8124]) // {
365 "yt-dlp" = { 400 "yt-dlp" = {
366 Socket = { 401 Socket = {
367 SocketMode = "0600"; 402 SocketMode = "0600";
@@ -375,7 +410,7 @@ in {
375 }; 410 };
376 }; 411 };
377 timers = { 412 timers = {
378 sync-keepass = { 413 "sync-keepass@store.kdbx" = {
379 Timer = { 414 Timer = {
380 OnActiveSec = "1m"; 415 OnActiveSec = "1m";
381 OnUnitActiveSec = "1m"; 416 OnUnitActiveSec = "1m";
@@ -385,6 +420,16 @@ in {
385 WantedBy = ["default.target"]; 420 WantedBy = ["default.target"];
386 }; 421 };
387 }; 422 };
423 "sync-keepass@rz.kdbx" = {
424 Timer = {
425 OnActiveSec = "1d";
426 OnUnitActiveSec = "1d";
427 };
428
429 Install = {
430 WantedBy = ["default.target"];
431 };
432 };
388 }; 433 };
389 targets = { 434 targets = {
390 graphical-session = { 435 graphical-session = {
diff --git a/accounts/gkleen@sif/utils/async-yt-dlp.nix b/accounts/gkleen@sif/utils/async-yt-dlp.nix
new file mode 100644
index 00000000..c3b82ec5
--- /dev/null
+++ b/accounts/gkleen@sif/utils/async-yt-dlp.nix
@@ -0,0 +1,57 @@
1{ writers, python3Packages, ... }:
2
3writers.writePython3Bin "async-yt-dlp" {
4 libraries = with python3Packages; [ yt-dlp ];
5 flakeIgnore = ["E501"];
6} ''
7import sys
8import os
9
10import yt_dlp
11import yt_dlp.options
12from collections import namedtuple
13import socket
14from pathlib import Path
15import json
16
17create_parser = yt_dlp.options.create_parser
18
19
20def parse_patched_options(opts):
21 patched_parser = create_parser()
22 patched_parser.defaults.update({
23 'ignoreerrors': False,
24 'retries': 0,
25 'fragment_retries': 0,
26 'extract_flat': False,
27 'concat_playlist': 'never',
28 })
29 yt_dlp.options.create_parser = lambda: patched_parser
30 try:
31 return yt_dlp.parse_options(opts)
32 finally:
33 yt_dlp.options.create_parser = create_parser
34
35
36default_opts = parse_patched_options([]).ydl_opts
37
38
39def cli_to_api(opts):
40 opts = parse_patched_options(opts)
41 urls = opts.urls
42 opts = opts.ydl_opts
43
44 diff = {k: v for k, v in opts.items() if default_opts[k] != v}
45 if 'postprocessors' in diff:
46 diff['postprocessors'] = [pp for pp in diff['postprocessors']
47 if pp not in default_opts['postprocessors']]
48 return namedtuple('Options', ('params', 'urls'))(diff, urls)
49
50
51if __name__ == '__main__':
52 opts = cli_to_api(sys.argv[1:])
53 with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
54 sock.connect(str(Path(os.environ["XDG_RUNTIME_DIR"]) / "yt-dlp.sock").encode('utf-8'))
55 with sock.makefile(mode='w', buffering=1, encoding='utf-8') as fh:
56 json.dump(opts._asdict(), fh)
57''
diff --git a/accounts/gkleen@sif/utils/sieve-edit.nix b/accounts/gkleen@sif/utils/sieve-edit.nix
new file mode 100644
index 00000000..f985a3f6
--- /dev/null
+++ b/accounts/gkleen@sif/utils/sieve-edit.nix
@@ -0,0 +1,24 @@
1pkgs@{ lib, resholve, zsh, sieve-connect, sops, ... }:
2
3resholve.writeScriptBin "sieve-edit" {
4 inputs = with pkgs; [sieve-connect sops];
5 interpreter = lib.getExe zsh;
6 execer = with pkgs; [
7 "cannot:${lib.getExe sieve-connect}"
8 "cannot:${lib.getExe sops}"
9 ];
10} ''
11 host=$1; shift
12 case "$host" in
13 surtr)
14 sieve-connect -s surtr.yggdrasil.li -m EXTERNAL --clientkey <(sops decrypt $HOME/projects/machines/hosts/surtr/email/ca/gkleen@sif.key) --clientcert $HOME/projects/machines/hosts/surtr/email/ca/gkleen@sif.crt --edit --remotesieve sieve
15 ;;
16 ymir)
17 sieve-connect -s ymir.yggdrasil.li -u gkleen --edit --remotesieve sieve
18 ;;
19 *)
20 echo "Unknown host: ‘$host’" >&2
21 return 2
22 ;;
23 esac
24''
diff --git a/accounts/gkleen@sif/zshrc b/accounts/gkleen@sif/zshrc
index e3f675a1..b24ff257 100644
--- a/accounts/gkleen@sif/zshrc
+++ b/accounts/gkleen@sif/zshrc
@@ -2,17 +2,14 @@ dir() {
2 curlArchive=false 2 curlArchive=false
3 templateArchive="" 3 templateArchive=""
4 repoUrl="" 4 repoUrl=""
5 nixShell=""
6 findNix=false
7 dir="" 5 dir=""
8 forceShell=false 6 forceShell=false
9 wormhole=false 7 wormhole=false
10 gitWorktree="" 8 gitWorktree=""
11 # notmuchMsg=""
12 quickserve=false
13 modifyPDF="" 9 modifyPDF=""
10 miniserve=false
14 11
15 while getopts ':t:a:s:Sd:ir:wqg:p:' arg; do 12 while getopts ':t:a:d:ir:wg:p:m' arg; do
16 case $arg in 13 case $arg in
17 "t") ;; 14 "t") ;;
18 "a") 15 "a")
@@ -23,16 +20,13 @@ dir() {
23 templateArchive=${OPTARG:a} 20 templateArchive=${OPTARG:a}
24 fi 21 fi
25 ;; 22 ;;
26 "s") nixShell=${OPTARG:a} ;;
27 "S") findNix=true ;;
28 "d") dir=${OPTARG} ;; 23 "d") dir=${OPTARG} ;;
29 "i") forceShell=true ;; 24 "i") forceShell=true ;;
30 "r") repoUrl=${OPTARG} ;; 25 "r") repoUrl=${OPTARG} ;;
31 "w") wormhole=true ;; 26 "w") wormhole=true ;;
32 "g") gitWorktree=${OPTARG} ;; 27 "g") gitWorktree=${OPTARG} ;;
33 # "n") notmuchMsg=${OPTARG} ;;
34 "q") quickserve=true ;;
35 "p") modifyPDF=${OPTARG:a} ;; 28 "p") modifyPDF=${OPTARG:a} ;;
29 "m") miniserve=true ;;
36 *) printf "Invalid option: %s\n" $arg >&2; exit 2 ;; 30 *) printf "Invalid option: %s\n" $arg >&2; exit 2 ;;
37 esac 31 esac
38 done 32 done
@@ -56,20 +50,34 @@ dir() {
56 gitWorktree="" 50 gitWorktree=""
57 fi 51 fi
58 52
53 miniservePIDFile=""
54 if [[ ${miniserve} = "true" ]]; then
55 miniservePIDFile=$(mktemp --tmpdir --suffix=.pid)
56 fi
57
59 cleanup() 58 cleanup()
60 { 59 {
61 cd ${modifyPDF:h} 60 if [[ -n ${modifyPDF} ]]; then
62 [[ -n ${modifyPDF} ]] && nix shell nixos#imagemagick -c convert -verbose ${dir}/${modifyPDF:t:r}_*.png(on) ${modifyPDF} 61 cd ${modifyPDF:h}
62 typeset -a pages
63 eval 'pages=(${dir}/${modifyPDF:t:r}_*.png(on))'
64 magick -verbose "$pages" ${modifyPDF}
65 modifyPDF=""
66 fi
67 if [[ -n ${miniservePIDFile} ]]; then
68 command kill --verbose -- $(cat ${miniservePIDFile}) && wait $(cat ${miniservePIDFile})
69 miniservePIDFile=""
70 fi
63 } 71 }
64 72
65 ( 73 (
74 set -o localoptions -o localtraps
75 trap 'return 1' INT TERM
66 trap cleanup EXIT 76 trap cleanup EXIT
67 77
68 cd ${dir} 78 cd ${dir}
69 export dir; 79 export dir;
70 80
71 ${findNix} && { nixShell=$(findNix) || return $? }
72
73 [[ -n ${repoUrl} ]] && git clone -- ${repoUrl} . 81 [[ -n ${repoUrl} ]] && git clone -- ${repoUrl} .
74 82
75 [[ -n ${modifyPDF} ]] && templateArchive=${modifyPDF} 83 [[ -n ${modifyPDF} ]] && templateArchive=${modifyPDF}
@@ -82,23 +90,23 @@ dir() {
82 } 90 }
83 trap cleanup EXIT 91 trap cleanup EXIT
84 92
85 if ${curlArchive}; then 93 if [[ $curlArchive = "true" ]]; then
86 archiveFile=$(mktemp -t "archive.XXXXXXXXXX.${templateArchive:t}") 94 archiveFile=$(mktemp -t "archive.XXXXXXXXXX.${templateArchive:t}")
87 95
88 curl -L -o ${archiveFile} ${templateArchive} 96 curl -SfL -o ${archiveFile} ${templateArchive}
89 97
90 templateArchive=${archiveFile} 98 templateArchive=${archiveFile}
91 fi 99 fi
92 100
93 unpack=true 101 unpack=true
94 while ${unpack}; do 102 while [[ $unpack = "true" ]]; do
95 case $(file --brief --mime-type --dereference ${templateArchive}) in 103 case $(file --brief --mime-type --dereference ${templateArchive}) in
96 application/zip) 104 application/zip)
97 unzip ${templateArchive} 105 unzip ${templateArchive}
98 unpack=false 106 unpack=false
99 ;; 107 ;;
100 application/vnd.debian.binary-package) 108 application/vnd.debian.binary-package)
101 nix shell nixos#binutils --command ar x ${templateArchive} 109 ar x ${templateArchive}
102 mkdir control data 110 mkdir control data
103 tar -C control -xvaf control.* 111 tar -C control -xvaf control.*
104 tar -C data -xvaf data.* 112 tar -C data -xvaf data.*
@@ -106,7 +114,7 @@ dir() {
106 ;; 114 ;;
107 application/x-rpm) 115 application/x-rpm)
108 cpioArchive=$(mktemp -t "archive.XXXXXXXXXX.${templateArchive:t:r}.cpio") 116 cpioArchive=$(mktemp -t "archive.XXXXXXXXXX.${templateArchive:t:r}.cpio")
109 nix shell nixos#busybox --command rpm2cpio ${templateArchive} > ${cpioArchive} 117 rpm2cpio ${templateArchive} > ${cpioArchive}
110 templateArchive=${cpioArchive} 118 templateArchive=${cpioArchive}
111 unpack=true 119 unpack=true
112 ;; 120 ;;
@@ -115,12 +123,12 @@ dir() {
115 unpack=false 123 unpack=false
116 ;; 124 ;;
117 application/pdf) 125 application/pdf)
118 nix shell nixos#ghostscript nixos#imagemagick -c convert -verbose -density 400 ${templateArchive} ${modifyPDF:t:r}_%0d.png 126 magick -verbose -density 400 ${templateArchive} ${modifyPDF:t:r}_%0d.png
119 unpack=false 127 unpack=false
120 ;; 128 ;;
121 application/octet-stream) 129 application/octet-stream)
122 if [[ $(file --brief --dereferenc ${templateArchive}) =~ Squashfs ]]; then 130 if [[ $(file --brief --dereference ${templateArchive}) =~ Squashfs ]]; then
123 nix shell nixos#squashfsTools -c unsquashfs -d . ${templateArchive} 131 unsquashfs -d . ${templateArchive}
124 unpack=false 132 unpack=false
125 fi 133 fi
126 ;; 134 ;;
@@ -134,25 +142,21 @@ dir() {
134 fi 142 fi
135 143
136 144
137 ${wormhole} && wormhole receive --accept-file 145 [[ $wormhole = "true" ]] && wormhole receive --accept-file
138 146
139 147
140 if ${quickserve}; then 148 if [[ ${#@} -gt 0 ]]; then
141 quickserve --root . --upload . --show-hidden --tar gz 149 ${@}
142 fi 150 fi
143 151
152 cd $(pwd) # Needed for mounting to work
144 153
145 if [[ ${#@} -eq 0 ]] || ${forceShell}; then 154 if [[ ${miniserve} = "true" ]]; then
146 if [[ ${#@} -gt 0 ]]; then 155 miniserve --random-route --hidden --enable-tar-gz --enable-zip . &
147 if [[ -z ${nixShell} ]]; then 156 echo $! > "${miniservePIDFile}"
148 ${@} 157 fi
149 else
150 nix-shell ${nixShell} --run "${@}"
151 fi
152 fi
153
154 cd $(pwd) # Needed for mounting to work
155 158
159 if [[ ${#@} -eq 0 ]] && [[ ${miniserve} != "true" ]] || [[ $forceShell = "true" ]]; then
156 isSingleDir() { 160 isSingleDir() {
157 typeset -a contents 161 typeset -a contents
158 contents=(*(N) .*(N)) 162 contents=(*(N) .*(N))
@@ -166,18 +170,9 @@ dir() {
166 } 170 }
167 while d=$(isSingleDir); do cd ${d}; done 171 while d=$(isSingleDir); do cd ${d}; done
168 172
169 173 zsh
170 if [[ -z ${nixShell} ]]; then 174 elif [[ ${miniserve} == "true" ]]; then
171 zsh 175 wait $(cat "${miniservePIDFile}")
172 else
173 nix-shell ${nixShell} --run zsh
174 fi
175 else
176 if [[ -z ${nixShell} ]]; then
177 ${@}
178 else
179 nix-shell ${nixShell} --run "${@}"
180 fi
181 fi 176 fi
182 ) 177 )
183} 178}
@@ -185,27 +180,30 @@ dir() {
185tmpdir() { 180tmpdir() {
186 cleanup() 181 cleanup()
187 { 182 {
188 cd / 183 cd /
189 unmount() { 184 unmount() {
190 printf "Unmounting %s\n" ${1} >&2 185 printf "Unmounting %s\n" ${1} >&2
191 fusermount -u ${1} || umount ${1} || sudo umount ${1} 186 fusermount -u ${1} || umount ${1} || sudo umount ${1}
192 } 187 }
193 188
194 if mountpoint -q -- ${dir}; then 189 if [[ -n ${dir} ]]; then
195 unmount ${dir} || return $? 190 if mountpoint -q -- ${dir}; then
196 else 191 unmount ${dir} || return $?
197 while read -d $'\0' subDir; do 192 else
198 mountpoint -q -- ${subDir} || continue 193 while read -d $'\0' subDir; do
199 unmount ${subDir} || return $? 194 mountpoint -q -- ${subDir} || continue
200 done <<<$(find ${dir} -xdev -type d -print0 | sort -zr) 195 unmount ${subDir} || return $?
201 fi 196 done <<<$(find ${dir} -xdev -type d -print0 | sort -zr)
202 197 fi
203 rm -rfv --one-file-system -- ${dir} 198
199 rm -rfv --one-file-system -- ${dir}
200 dir=""
201 fi
204 } 202 }
205 203
206 local tmpdir="" 204 local tmpdir=""
207 205
208 while getopts ':t:a:s:Sd:ir:wqg:p:' arg; do 206 while getopts ':t:a:d:ir:wg:p:m' arg; do
209 case $arg in 207 case $arg in
210 "t") tmpdir="=${OPTARG}" ;; 208 "t") tmpdir="=${OPTARG}" ;;
211 "?"|":") printf "Invalid option: %s\n" $arg >&2; exit 2 ;; 209 "?"|":") printf "Invalid option: %s\n" $arg >&2; exit 2 ;;
@@ -213,6 +211,8 @@ tmpdir() {
213 done 211 done
214 212
215 ( 213 (
214 set -o localoptions -o localtraps
215 trap 'return 1' INT TERM
216 trap cleanup EXIT 216 trap cleanup EXIT
217 217
218 218
@@ -231,17 +231,7 @@ clock() {
231} 231}
232 232
233public-ip() { 233public-ip() {
234 curl -s -H 'Accept: application/json' $@ ifconfig.co | jq -r '.ip' 234 curl -sSf -H 'Accept: application/json' $@ ifconfig.co | jq -r '.ip'
235}
236
237nix-ghci() {
238 pkgExpr=""
239 if [[ ${#@} -gt 0 ]]; then
240 pkgExpr="${1}"
241 shift
242 fi
243
244 nix-shell -p "with (import <nixpkgs> {}); pkgs.haskellPackages.ghcWithPackages (p: with p; [${pkgExpr}])" --run "ghci ${@}"
245} 235}
246 236
247swap() { 237swap() {
@@ -271,14 +261,6 @@ l() {
271 ls --long --binary --git --time-style=iso --header $@ 261 ls --long --binary --git --time-style=iso --header $@
272} 262}
273 263
274re() {
275 systemctl --restart $@
276}
277
278ure() {
279 systemctl --user --restart $@
280}
281
282ssh-installer() { 264ssh-installer() {
283 ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentityFile=~/.ssh/gkleen@sif.midgard.yggdrasil $@ 265 ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentityFile=~/.ssh/gkleen@sif.midgard.yggdrasil $@
284} 266}
@@ -306,20 +288,7 @@ done < <(find ~/projects ~/uni -regextype posix-extended -maxdepth 2 -type d -re
306 sed -zr 's|(.*/([0-9]{2}[ws])/(.+))|\1 \2 \3|' | \ 288 sed -zr 's|(.*/([0-9]{2}[ws])/(.+))|\1 \2 \3|' | \
307 sort -z -r -k2 | sort -z -s -k3 | uniq -z -f 2) 289 sort -z -r -k2 | sort -z -s -k3 | uniq -z -f 2)
308 290
309alias '..'='cd ..'
310alias rzadm=$'tmpdir -i sh -c \'mkdir adm; sshfs gkleen@mgmt01:/adm adm\'' 291alias rzadm=$'tmpdir -i sh -c \'mkdir adm; sshfs gkleen@mgmt01:/adm adm\''
311alias mathcloud=$'tmpdir -i rclone mount --daemon mathcloud:// .' 292alias mathcloud=$'tmpdir -i rclone mount --daemon mathcloud:// .'
312alias -g L='| less'
313alias -g S='&> /dev/null'
314alias -g G='| grep'
315alias -g B='&> /dev/null &'
316alias -g BB='&> /dev/null &!'
317 293
318export DEFAULT_USER=gkleen 294export DEFAULT_USER=gkleen
319
320bindkey -e
321bindkey ';5C' emacs-forward-word
322bindkey ';5D' emacs-backward-word
323bindkey '^[[1;5C' emacs-forward-word
324bindkey '^[[1;5D' emacs-backward-word
325bindkey '^H' backward-kill-word
diff --git a/accounts/gkleen@surtr.nix b/accounts/gkleen@surtr.nix
index 58c4f21d..8f678ac9 100644
--- a/accounts/gkleen@surtr.nix
+++ b/accounts/gkleen@surtr.nix
@@ -1,3 +1,7 @@
1{ userName, ... }: { 1{ flake, userName, ... }: {
2 home-manager.users.${userName}.home.stateVersion = "20.09"; 2 imports = with flake.nixosModules.userProfiles.${userName}; [
3 zsh tmux
4 ];
5
6 config.home-manager.users.${userName}.home.stateVersion = "20.09";
3} 7}
diff --git a/accounts/gkleen@vidhar.nix b/accounts/gkleen@vidhar.nix
index 8509c2f4..3a37c4bd 100644
--- a/accounts/gkleen@vidhar.nix
+++ b/accounts/gkleen@vidhar.nix
@@ -1,4 +1,8 @@
1{ flake, pkgs, userName, config, ... }: { 1{ flake, pkgs, userName, config, ... }: {
2 imports = with flake.nixosModules.userProfiles.${userName}; [
3 zsh tmux
4 ];
5
2 config = { 6 config = {
3 users.users.${userName} = { 7 users.users.${userName} = {
4 uid = 1000; 8 uid = 1000;
diff --git a/accounts/mherold@eostre.nix b/accounts/mherold@eostre.nix
index 51e4529a..0e2f37aa 100644
--- a/accounts/mherold@eostre.nix
+++ b/accounts/mherold@eostre.nix
@@ -7,9 +7,9 @@
7 home-manager.users.${userName} = { 7 home-manager.users.${userName} = {
8 home.stateVersion = "20.09"; 8 home.stateVersion = "20.09";
9 9
10 nixpkgs.config = { 10 # nixpkgs.config = {
11 allowUnfree = true; 11 # allowUnfree = true;
12 }; 12 # };
13 13
14 home.packages = with pkgs; [ 14 home.packages = with pkgs; [
15 thunderbird libreoffice element-desktop keepassxc vlc 15 thunderbird libreoffice element-desktop keepassxc vlc
diff --git a/accounts/root@installer.nix b/accounts/root@installer.nix
index c7a418f8..5fe1db38 100644
--- a/accounts/root@installer.nix
+++ b/accounts/root@installer.nix
@@ -1,7 +1,11 @@
1{ userName, ... }: 1{ flake, userName, ... }:
2 2
3{ 3{
4 home-manager.users.${userName} = { config, ... } : { 4 imports = with flake.nixosModules.userProfiles.${userName}; [
5 zsh tmux
6 ];
7
8 config.home-manager.users.${userName} = { config, ... } : {
5 home.stateVersion = config.home.version.release; 9 home.stateVersion = config.home.version.release;
6 }; 10 };
7} 11}
diff --git a/accounts/root@sif.nix b/accounts/root@sif.nix
index c9e129a0..bb816230 100644
--- a/accounts/root@sif.nix
+++ b/accounts/root@sif.nix
@@ -1,6 +1,10 @@
1{ userName, ... }: 1{ flake, userName, ... }:
2{ 2{
3 home-manager.users.${userName} = { 3 imports = with flake.nixosModules.userProfiles.${userName}; [
4 zsh tmux
5 ];
6
7 config.home-manager.users.${userName} = {
4 home.stateVersion = "20.09"; 8 home.stateVersion = "20.09";
5 9
6 programs.ssh.matchBlocks = { 10 programs.ssh.matchBlocks = {
diff --git a/accounts/root@surtr.nix b/accounts/root@surtr.nix
index 58c4f21d..8f678ac9 100644
--- a/accounts/root@surtr.nix
+++ b/accounts/root@surtr.nix
@@ -1,3 +1,7 @@
1{ userName, ... }: { 1{ flake, userName, ... }: {
2 home-manager.users.${userName}.home.stateVersion = "20.09"; 2 imports = with flake.nixosModules.userProfiles.${userName}; [
3 zsh tmux
4 ];
5
6 config.home-manager.users.${userName}.home.stateVersion = "20.09";
3} 7}
diff --git a/accounts/root@vidhar.nix b/accounts/root@vidhar.nix
index e82414a8..0fc56633 100644
--- a/accounts/root@vidhar.nix
+++ b/accounts/root@vidhar.nix
@@ -1,6 +1,11 @@
1{ config, userName, ... }: 1{ flake, config, userName, ... }:
2
2{ 3{
3 home-manager.users.${userName} = { 4 imports = with flake.nixosModules.userProfiles.${userName}; [
5 zsh tmux
6 ];
7
8 config.home-manager.users.${userName} = {
4 home.stateVersion = "20.09"; 9 home.stateVersion = "20.09";
5 10
6 programs.ssh.matchBlocks = { 11 programs.ssh.matchBlocks = {
diff --git a/flake.lock b/flake.lock
index 1764d172..404d81b6 100644
--- a/flake.lock
+++ b/flake.lock
@@ -6,22 +6,28 @@
6 "nixpkgs": [ 6 "nixpkgs": [
7 "nixpkgs" 7 "nixpkgs"
8 ], 8 ],
9 "poetry2nix": [ 9 "pre-commit-hooks-nix": "pre-commit-hooks-nix",
10 "poetry2nix" 10 "pyproject-build-systems": [
11 "pyproject-build-systems"
12 ],
13 "pyproject-nix": [
14 "pyproject-nix"
11 ], 15 ],
12 "pre-commit-hooks-nix": "pre-commit-hooks-nix" 16 "uv2nix": [
17 "uv2nix"
18 ]
13 }, 19 },
14 "locked": { 20 "locked": {
15 "lastModified": 1723124245, 21 "lastModified": 1749560907,
16 "narHash": "sha256-ThDq7vOXo6G4+C5FHqUc64CeX2c5n36tln4ZlDao6s4=", 22 "narHash": "sha256-zvAxxnJ5dcnjbuog0W6UvlthD1dLm5t4ZQI25jzNDW4=",
17 "owner": "gkleen", 23 "owner": "gkleen",
18 "repo": "backup-utils", 24 "repo": "backup-utils",
19 "rev": "74e65090de63fc99f056098677cc490754e2708f", 25 "rev": "8ed6a1d7c2e337cb2e35ed68f6d852f0d1049908",
20 "type": "gitlab" 26 "type": "gitlab"
21 }, 27 },
22 "original": { 28 "original": {
23 "owner": "gkleen", 29 "owner": "gkleen",
24 "ref": "v0.1.6", 30 "ref": "v0.1.7",
25 "repo": "backup-utils", 31 "repo": "backup-utils",
26 "type": "gitlab" 32 "type": "gitlab"
27 } 33 }
@@ -33,22 +39,26 @@
33 "nixpkgs": [ 39 "nixpkgs": [
34 "nixpkgs" 40 "nixpkgs"
35 ], 41 ],
36 "poetry2nix": [ 42 "pre-commit-hooks-nix": "pre-commit-hooks-nix_2",
37 "poetry2nix" 43 "pyproject-build-systems": "pyproject-build-systems",
44 "pyproject-nix": [
45 "pyproject-nix"
38 ], 46 ],
39 "pre-commit-hooks-nix": "pre-commit-hooks-nix_2" 47 "uv2nix": [
48 "uv2nix"
49 ]
40 }, 50 },
41 "locked": { 51 "locked": {
42 "lastModified": 1734281899, 52 "lastModified": 1750599403,
43 "narHash": "sha256-9QdIl3sjHY4Xij9KrBUkW1KpLB+jyxlI12UHPitlawI=", 53 "narHash": "sha256-MLQ7CISl00w1xq88TL2wukNq3ukzID4u7BVT4okbUik=",
44 "owner": "gkleen", 54 "owner": "gkleen",
45 "repo": "ca", 55 "repo": "ca",
46 "rev": "1e4ee9d25a5282ef7bc6774072229784fa0036f3", 56 "rev": "505a29233ada969b2eca76d616b0d7a8767dfb71",
47 "type": "gitlab" 57 "type": "gitlab"
48 }, 58 },
49 "original": { 59 "original": {
50 "owner": "gkleen", 60 "owner": "gkleen",
51 "ref": "v3.1.3", 61 "ref": "v3.1.5",
52 "repo": "ca", 62 "repo": "ca",
53 "type": "gitlab" 63 "type": "gitlab"
54 } 64 }
@@ -66,11 +76,11 @@
66 ] 76 ]
67 }, 77 },
68 "locked": { 78 "locked": {
69 "lastModified": 1727447169, 79 "lastModified": 1749105467,
70 "narHash": "sha256-3KyjMPUKHkiWhwR91J1YchF6zb6gvckCAY1jOE+ne0U=", 80 "narHash": "sha256-hXh76y/wDl15almBcqvjryB50B0BaiXJKk20f314RoE=",
71 "owner": "serokell", 81 "owner": "serokell",
72 "repo": "deploy-rs", 82 "repo": "deploy-rs",
73 "rev": "aa07eb05537d4cd025e2310397a6adcedfe72c76", 83 "rev": "6bc76b872374845ba9d645a2f012b764fecd765f",
74 "type": "github" 84 "type": "github"
75 }, 85 },
76 "original": { 86 "original": {
@@ -115,11 +125,11 @@
115 "flake-compat_3": { 125 "flake-compat_3": {
116 "flake": false, 126 "flake": false,
117 "locked": { 127 "locked": {
118 "lastModified": 1733328505, 128 "lastModified": 1747046372,
119 "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=", 129 "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
120 "owner": "edolstra", 130 "owner": "edolstra",
121 "repo": "flake-compat", 131 "repo": "flake-compat",
122 "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", 132 "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
123 "type": "github" 133 "type": "github"
124 }, 134 },
125 "original": { 135 "original": {
@@ -168,11 +178,11 @@
168 "nixpkgs-lib": "nixpkgs-lib_2" 178 "nixpkgs-lib": "nixpkgs-lib_2"
169 }, 179 },
170 "locked": { 180 "locked": {
171 "lastModified": 1733312601, 181 "lastModified": 1749398372,
172 "narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=", 182 "narHash": "sha256-tYBdgS56eXYaWVW3fsnPQ/nFlgWi/Z2Ymhyu21zVM98=",
173 "owner": "hercules-ci", 183 "owner": "hercules-ci",
174 "repo": "flake-parts", 184 "repo": "flake-parts",
175 "rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9", 185 "rev": "9305fe4e5c2a6fcf5ba6a3ff155720fbe4076569",
176 "type": "github" 186 "type": "github"
177 }, 187 },
178 "original": { 188 "original": {
@@ -202,11 +212,11 @@
202 "flake-registry": { 212 "flake-registry": {
203 "flake": false, 213 "flake": false,
204 "locked": { 214 "locked": {
205 "lastModified": 1734450202, 215 "lastModified": 1744623129,
206 "narHash": "sha256-/3gigrEBFORQs6a8LL5twoHs7biu08y/8Xc5aQmk3b0=", 216 "narHash": "sha256-nlQTQrHqM+ywXN0evDXnYEV6z6WWZB5BFQ2TkXsduKw=",
207 "owner": "NixOS", 217 "owner": "NixOS",
208 "repo": "flake-registry", 218 "repo": "flake-registry",
209 "rev": "02fe640c9e117dd9d6a34efc7bcb8bd09c08111d", 219 "rev": "1322f33d5836ae757d2e6190239252cf8402acf6",
210 "type": "github" 220 "type": "github"
211 }, 221 },
212 "original": { 222 "original": {
@@ -322,11 +332,11 @@
322 ] 332 ]
323 }, 333 },
324 "locked": { 334 "locked": {
325 "lastModified": 1737831749, 335 "lastModified": 1746904907,
326 "narHash": "sha256-La1xZYZ1yHvT4h5MNl5WC2wxBi2P4vozce2n7V/9+2w=", 336 "narHash": "sha256-XYo6bwc7xwo4lO6a/D2ttYRN4yDmsAjyt5O1E0vOLDg=",
327 "owner": "gkleen", 337 "owner": "gkleen",
328 "repo": "home-manager", 338 "repo": "home-manager",
329 "rev": "8b16ee252e38acc29ba634ab60672a051ebc9f59", 339 "rev": "696495266c65b76f08d8196b87aa7bd835906570",
330 "type": "github" 340 "type": "github"
331 }, 341 },
332 "original": { 342 "original": {
@@ -343,16 +353,16 @@
343 ] 353 ]
344 }, 354 },
345 "locked": { 355 "locked": {
346 "lastModified": 1710245356, 356 "lastModified": 1749562430,
347 "narHash": "sha256-8cQGUn+N1dTgklMWMejSLN2q8Oz+7Rnqsfaw2rt3bU4=", 357 "narHash": "sha256-M5MqsIsf+o7yngakVUW4poBGZaghB6sUpw7SsWA55kU=",
348 "owner": "gkleen", 358 "owner": "gkleen",
349 "repo": "home-manager", 359 "repo": "home-manager",
350 "rev": "a14fe0c27d04dfa3d80abe2db743e9a7f4f2a33d", 360 "rev": "dca5a2df9f8a00cc34bea6ead249a8446f5f069e",
351 "type": "github" 361 "type": "github"
352 }, 362 },
353 "original": { 363 "original": {
354 "owner": "gkleen", 364 "owner": "gkleen",
355 "ref": "nixos-late-start-23.11", 365 "ref": "nixos-late-start-25.05",
356 "repo": "home-manager", 366 "repo": "home-manager",
357 "type": "github" 367 "type": "github"
358 } 368 }
@@ -376,7 +386,7 @@
376 "leapseconds": { 386 "leapseconds": {
377 "flake": false, 387 "flake": false,
378 "locked": { 388 "locked": {
379 "narHash": "sha256-5ZaoY/bScQS7EGJRHu6vj9XWhbObmxNEaGugaGU7+lg=", 389 "narHash": "sha256-FJgbafPB48+5sT+7ZB8pajSsfJoISEOoaJ0d/2Ya7o8=",
380 "type": "file", 390 "type": "file",
381 "url": "https://data.iana.org/time-zones/tzdb/leap-seconds.list" 391 "url": "https://data.iana.org/time-zones/tzdb/leap-seconds.list"
382 }, 392 },
@@ -397,11 +407,11 @@
397 "xwayland-satellite-unstable": "xwayland-satellite-unstable" 407 "xwayland-satellite-unstable": "xwayland-satellite-unstable"
398 }, 408 },
399 "locked": { 409 "locked": {
400 "lastModified": 1737840481, 410 "lastModified": 1752078530,
401 "narHash": "sha256-WjW3cdrmh1sGMT3CBqCFzT9BOktTa1u9ldoWEqTj7xk=", 411 "narHash": "sha256-TrRmlYdhWcadWvBpDjB9Xlry4uT4ZUIO46d+o5tjtCQ=",
402 "owner": "sodiboo", 412 "owner": "sodiboo",
403 "repo": "niri-flake", 413 "repo": "niri-flake",
404 "rev": "8fc9dba8df75d9d004d9369b513b81180788ec15", 414 "rev": "d231d92313192d4d0c78d6ef04167fed9dee87cf",
405 "type": "github" 415 "type": "github"
406 }, 416 },
407 "original": { 417 "original": {
@@ -414,16 +424,16 @@
414 "niri-stable": { 424 "niri-stable": {
415 "flake": false, 425 "flake": false,
416 "locked": { 426 "locked": {
417 "lastModified": 1736614405, 427 "lastModified": 1748151941,
418 "narHash": "sha256-AJ1rlgNOPb3/+DbS5hkhm21t6Oz8IgqLllwmZt0lyzk=", 428 "narHash": "sha256-z4viQZLgC2bIJ3VrzQnR+q2F3gAOEQpU1H5xHtX/2fs=",
419 "owner": "YaLTeR", 429 "owner": "YaLTeR",
420 "repo": "niri", 430 "repo": "niri",
421 "rev": "e05bc269e678ecf828b96ae79c991c13b00b38a5", 431 "rev": "8ba57fcf25d2fc9565131684a839d58703f1dae7",
422 "type": "github" 432 "type": "github"
423 }, 433 },
424 "original": { 434 "original": {
425 "owner": "YaLTeR", 435 "owner": "YaLTeR",
426 "ref": "v25.01", 436 "ref": "v25.05.1",
427 "repo": "niri", 437 "repo": "niri",
428 "type": "github" 438 "type": "github"
429 } 439 }
@@ -431,15 +441,15 @@
431 "niri-unstable": { 441 "niri-unstable": {
432 "flake": false, 442 "flake": false,
433 "locked": { 443 "locked": {
434 "lastModified": 1737795105, 444 "lastModified": 1750791124,
435 "narHash": "sha256-OsrjQ8O9t9NjDCwfG/EY8MT+K3lb+A5U6SZZ+4PyKzk=", 445 "narHash": "sha256-F5iVU/hjoSHSSe0gllxm0PcAaseEtGNanYK5Ha3k2Tg=",
436 "owner": "gkleen", 446 "owner": "YaLTeR",
437 "repo": "niri", 447 "repo": "niri",
438 "rev": "78697d1cea20e6b53013e820999b0403c45d9f00", 448 "rev": "37458d94b288945f6cfbd3c5c233f634d59f246c",
439 "type": "github" 449 "type": "github"
440 }, 450 },
441 "original": { 451 "original": {
442 "owner": "gkleen", 452 "owner": "YaLTeR",
443 "repo": "niri", 453 "repo": "niri",
444 "type": "github" 454 "type": "github"
445 } 455 }
@@ -472,11 +482,11 @@
472 ] 482 ]
473 }, 483 },
474 "locked": { 484 "locked": {
475 "lastModified": 1737257306, 485 "lastModified": 1751774635,
476 "narHash": "sha256-lEGgpA4kGafc76+Amnz+gh1L/cwUS2pePFlf22WEyh8=", 486 "narHash": "sha256-DuOznGdgMxeSlPpUu6Wkq0ZD5e2Cfv9XRZeZlHWMd1s=",
477 "owner": "Mic92", 487 "owner": "Mic92",
478 "repo": "nix-index-database", 488 "repo": "nix-index-database",
479 "rev": "744d330659e207a1883d2da0141d35e520eb87bd", 489 "rev": "85686025ba6d18df31cc651a91d5adef63378978",
480 "type": "github" 490 "type": "github"
481 }, 491 },
482 "original": { 492 "original": {
@@ -486,6 +496,27 @@
486 "type": "github" 496 "type": "github"
487 } 497 }
488 }, 498 },
499 "nix-monitored": {
500 "inputs": {
501 "nixpkgs": [
502 "nixpkgs"
503 ]
504 },
505 "locked": {
506 "lastModified": 1745680380,
507 "narHash": "sha256-Z8PknjkmIr/8ZCH+dmc2Pc+UltiOr7/oKg37PXuVvuU=",
508 "owner": "ners",
509 "repo": "nix-monitored",
510 "rev": "60f3baa4701d58eab86c2d1d9c3d7e820074d461",
511 "type": "github"
512 },
513 "original": {
514 "owner": "ners",
515 "ref": "master",
516 "repo": "nix-monitored",
517 "type": "github"
518 }
519 },
489 "nixVirt": { 520 "nixVirt": {
490 "inputs": { 521 "inputs": {
491 "nixpkgs": [ 522 "nixpkgs": [
@@ -493,11 +524,11 @@
493 ] 524 ]
494 }, 525 },
495 "locked": { 526 "locked": {
496 "lastModified": 1736736253, 527 "lastModified": 1748140003,
497 "narHash": "sha256-GrktftEfXmmdKOU0yz3QXckDz1ncZ+f4KLU8XnYKYuA=", 528 "narHash": "sha256-DNBZmuk1YRM2PmwbHzVdXumRjCUzQkMarg4iI/37rOQ=",
498 "owner": "AshleyYakeley", 529 "owner": "AshleyYakeley",
499 "repo": "NixVirt", 530 "repo": "NixVirt",
500 "rev": "9063243af5e6674359a0ff7cec57f02eeacf0cea", 531 "rev": "5dfe108fd859b122f9a96981cb6bc12297653d6c",
501 "type": "github" 532 "type": "github"
502 }, 533 },
503 "original": { 534 "original": {
@@ -508,11 +539,11 @@
508 }, 539 },
509 "nixos-hardware": { 540 "nixos-hardware": {
510 "locked": { 541 "locked": {
511 "lastModified": 1737751639, 542 "lastModified": 1752048960,
512 "narHash": "sha256-ZEbOJ9iT72iwqXsiEMbEa8wWjyFvRA9Ugx8utmYbpz4=", 543 "narHash": "sha256-gATnkOe37eeVwKKYCsL+OnS2gU4MmLuZFzzWCtaKLI8=",
513 "owner": "NixOS", 544 "owner": "NixOS",
514 "repo": "nixos-hardware", 545 "repo": "nixos-hardware",
515 "rev": "dfad538f751a5aa5d4436d9781ab27a6128ec9d4", 546 "rev": "7ced9122cff2163c6a0212b8d1ec8c33a1660806",
516 "type": "github" 547 "type": "github"
517 }, 548 },
518 "original": { 549 "original": {
@@ -540,16 +571,16 @@
540 }, 571 },
541 "nixpkgs-eostre": { 572 "nixpkgs-eostre": {
542 "locked": { 573 "locked": {
543 "lastModified": 1701282334, 574 "lastModified": 1748026580,
544 "narHash": "sha256-MxCVrXY6v4QmfTwIysjjaX0XUhqBbxTWWB4HXtDYsdk=", 575 "narHash": "sha256-rWtXrcIzU5wm/C8F9LWvUfBGu5U5E7cFzPYT1pHIJaQ=",
545 "owner": "NixOS", 576 "owner": "NixOS",
546 "repo": "nixpkgs", 577 "repo": "nixpkgs",
547 "rev": "057f9aecfb71c4437d2b27d3323df7f93c010b7e", 578 "rev": "11cb3517b3af6af300dd6c055aeda73c9bf52c48",
548 "type": "github" 579 "type": "github"
549 }, 580 },
550 "original": { 581 "original": {
551 "owner": "NixOS", 582 "owner": "NixOS",
552 "ref": "23.11", 583 "ref": "25.05",
553 "repo": "nixpkgs", 584 "repo": "nixpkgs",
554 "type": "github" 585 "type": "github"
555 } 586 }
@@ -568,14 +599,17 @@
568 }, 599 },
569 "nixpkgs-lib_2": { 600 "nixpkgs-lib_2": {
570 "locked": { 601 "locked": {
571 "lastModified": 1733096140, 602 "lastModified": 1748740939,
572 "narHash": "sha256-1qRH7uAUsyQI7R1Uwl4T+XvdNv778H0Nb5njNrqvylY=", 603 "narHash": "sha256-rQaysilft1aVMwF14xIdGS3sj1yHlI6oKQNBRTF40cc=",
573 "type": "tarball", 604 "owner": "nix-community",
574 "url": "https://github.com/NixOS/nixpkgs/archive/5487e69da40cbd611ab2cadee0b4637225f7cfae.tar.gz" 605 "repo": "nixpkgs.lib",
606 "rev": "656a64127e9d791a334452c6b6606d17539476e2",
607 "type": "github"
575 }, 608 },
576 "original": { 609 "original": {
577 "type": "tarball", 610 "owner": "nix-community",
578 "url": "https://github.com/NixOS/nixpkgs/archive/5487e69da40cbd611ab2cadee0b4637225f7cfae.tar.gz" 611 "repo": "nixpkgs.lib",
612 "type": "github"
579 } 613 }
580 }, 614 },
581 "nixpkgs-lib_3": { 615 "nixpkgs-lib_3": {
@@ -630,32 +664,32 @@
630 }, 664 },
631 "nixpkgs-stable_2": { 665 "nixpkgs-stable_2": {
632 "locked": { 666 "locked": {
633 "lastModified": 1737672001, 667 "lastModified": 1751943650,
634 "narHash": "sha256-YnHJJ19wqmibLQdUeq9xzE6CjrMA568KN/lFPuSVs4I=", 668 "narHash": "sha256-7orTnNqkGGru8Je6Un6mq1T8YVVU/O5kyW4+f9C1mZQ=",
635 "owner": "NixOS", 669 "owner": "NixOS",
636 "repo": "nixpkgs", 670 "repo": "nixpkgs",
637 "rev": "035f8c0853c2977b24ffc4d0a42c74f00b182cd8", 671 "rev": "88983d4b665fb491861005137ce2b11a9f89f203",
638 "type": "github" 672 "type": "github"
639 }, 673 },
640 "original": { 674 "original": {
641 "owner": "NixOS", 675 "owner": "NixOS",
642 "ref": "nixos-24.11", 676 "ref": "nixos-25.05",
643 "repo": "nixpkgs", 677 "repo": "nixpkgs",
644 "type": "github" 678 "type": "github"
645 } 679 }
646 }, 680 },
647 "nixpkgs-stable_3": { 681 "nixpkgs-stable_3": {
648 "locked": { 682 "locked": {
649 "lastModified": 1717179513, 683 "lastModified": 1748026580,
650 "narHash": "sha256-vboIEwIQojofItm2xGCdZCzW96U85l9nDW3ifMuAIdM=", 684 "narHash": "sha256-rWtXrcIzU5wm/C8F9LWvUfBGu5U5E7cFzPYT1pHIJaQ=",
651 "owner": "NixOS", 685 "owner": "NixOS",
652 "repo": "nixpkgs", 686 "repo": "nixpkgs",
653 "rev": "63dacb46bf939521bdc93981b4cbb7ecb58427a0", 687 "rev": "11cb3517b3af6af300dd6c055aeda73c9bf52c48",
654 "type": "github" 688 "type": "github"
655 }, 689 },
656 "original": { 690 "original": {
657 "owner": "NixOS", 691 "owner": "NixOS",
658 "ref": "24.05", 692 "ref": "25.05",
659 "repo": "nixpkgs", 693 "repo": "nixpkgs",
660 "type": "github" 694 "type": "github"
661 } 695 }
@@ -678,11 +712,11 @@
678 }, 712 },
679 "nixpkgs_2": { 713 "nixpkgs_2": {
680 "locked": { 714 "locked": {
681 "lastModified": 1737842646, 715 "lastModified": 1751984180,
682 "narHash": "sha256-Bw3D+zXAGxcaS32BgXv3A/uLDEXn6/a18cX41USsv+M=", 716 "narHash": "sha256-LwWRsENAZJKUdD3SpLluwDmdXY9F45ZEgCb0X+xgOL0=",
683 "owner": "nixos", 717 "owner": "NixOS",
684 "repo": "nixpkgs", 718 "repo": "nixpkgs",
685 "rev": "34995559351f3b61c122e5566f1903d500e9b890", 719 "rev": "9807714d6944a957c2e036f84b0ff8caf9930bc0",
686 "type": "github" 720 "type": "github"
687 }, 721 },
688 "original": { 722 "original": {
@@ -748,11 +782,11 @@
748 "treefmt-nix": "treefmt-nix" 782 "treefmt-nix": "treefmt-nix"
749 }, 783 },
750 "locked": { 784 "locked": {
751 "lastModified": 1736884309, 785 "lastModified": 1743690424,
752 "narHash": "sha256-eiCqmKl0BIRiYk5/ZhZozwn4/7Km9CWTbc15Cv+VX5k=", 786 "narHash": "sha256-cX98bUuKuihOaRp8dNV1Mq7u6/CQZWTPth2IJPATBXc=",
753 "owner": "nix-community", 787 "owner": "nix-community",
754 "repo": "poetry2nix", 788 "repo": "poetry2nix",
755 "rev": "75d0515332b7ca269f6d7abfd2c44c47a7cbca7b", 789 "rev": "ce2369db77f45688172384bbeb962bc6c2ea6f94",
756 "type": "github" 790 "type": "github"
757 }, 791 },
758 "original": { 792 "original": {
@@ -790,18 +824,14 @@
790 "nixpkgs": [ 824 "nixpkgs": [
791 "ca-util", 825 "ca-util",
792 "nixpkgs" 826 "nixpkgs"
793 ],
794 "nixpkgs-stable": [
795 "ca-util",
796 "nixpkgs"
797 ] 827 ]
798 }, 828 },
799 "locked": { 829 "locked": {
800 "lastModified": 1734261738, 830 "lastModified": 1749636823,
801 "narHash": "sha256-3Lzk+7QyX8v60+km26D3dln7NMSA13vW+KYTkMkds6Q=", 831 "narHash": "sha256-WUaIlOlPLyPgz9be7fqWJA5iG6rHcGRtLERSCfUDne4=",
802 "owner": "cachix", 832 "owner": "cachix",
803 "repo": "pre-commit-hooks.nix", 833 "repo": "pre-commit-hooks.nix",
804 "rev": "4c8e75efbbdcc6f9203f64b1f21f8a55d2285264", 834 "rev": "623c56286de5a3193aa38891a6991b28f9bab056",
805 "type": "github" 835 "type": "github"
806 }, 836 },
807 "original": { 837 "original": {
@@ -858,6 +888,81 @@
858 "type": "gitlab" 888 "type": "gitlab"
859 } 889 }
860 }, 890 },
891 "pyproject-build-systems": {
892 "inputs": {
893 "nixpkgs": [
894 "ca-util",
895 "nixpkgs"
896 ],
897 "pyproject-nix": [
898 "ca-util",
899 "pyproject-nix"
900 ],
901 "uv2nix": [
902 "ca-util",
903 "uv2nix"
904 ]
905 },
906 "locked": {
907 "lastModified": 1749519371,
908 "narHash": "sha256-UJONN7mA2stweZCoRcry2aa1XTTBL0AfUOY84Lmqhos=",
909 "owner": "pyproject-nix",
910 "repo": "build-system-pkgs",
911 "rev": "7c06967eca687f3482624250428cc12f43c92523",
912 "type": "github"
913 },
914 "original": {
915 "owner": "pyproject-nix",
916 "repo": "build-system-pkgs",
917 "type": "github"
918 }
919 },
920 "pyproject-build-systems_2": {
921 "inputs": {
922 "nixpkgs": [
923 "nixpkgs"
924 ],
925 "pyproject-nix": [
926 "pyproject-nix"
927 ],
928 "uv2nix": [
929 "uv2nix"
930 ]
931 },
932 "locked": {
933 "lastModified": 1749519371,
934 "narHash": "sha256-UJONN7mA2stweZCoRcry2aa1XTTBL0AfUOY84Lmqhos=",
935 "owner": "pyproject-nix",
936 "repo": "build-system-pkgs",
937 "rev": "7c06967eca687f3482624250428cc12f43c92523",
938 "type": "github"
939 },
940 "original": {
941 "owner": "pyproject-nix",
942 "repo": "build-system-pkgs",
943 "type": "github"
944 }
945 },
946 "pyproject-nix": {
947 "inputs": {
948 "nixpkgs": [
949 "nixpkgs"
950 ]
951 },
952 "locked": {
953 "lastModified": 1751557494,
954 "narHash": "sha256-dnueIffmEKtG0V4feifalsOYaHXXsrGMaoKI+4O7v/8=",
955 "owner": "pyproject-nix",
956 "repo": "pyproject.nix",
957 "rev": "939ef94aea81c17bfd2f388465309eab76c45c37",
958 "type": "github"
959 },
960 "original": {
961 "owner": "pyproject-nix",
962 "repo": "pyproject.nix",
963 "type": "github"
964 }
965 },
861 "root": { 966 "root": {
862 "inputs": { 967 "inputs": {
863 "backup-utils": "backup-utils", 968 "backup-utils": "backup-utils",
@@ -871,6 +976,7 @@
871 "impermanence": "impermanence", 976 "impermanence": "impermanence",
872 "niri-flake": "niri-flake", 977 "niri-flake": "niri-flake",
873 "nix-index-database": "nix-index-database", 978 "nix-index-database": "nix-index-database",
979 "nix-monitored": "nix-monitored",
874 "nixVirt": "nixVirt", 980 "nixVirt": "nixVirt",
875 "nixos-hardware": "nixos-hardware", 981 "nixos-hardware": "nixos-hardware",
876 "nixpkgs": "nixpkgs_2", 982 "nixpkgs": "nixpkgs_2",
@@ -880,7 +986,10 @@
880 "nvfetcher": "nvfetcher", 986 "nvfetcher": "nvfetcher",
881 "poetry2nix": "poetry2nix", 987 "poetry2nix": "poetry2nix",
882 "prometheus-borg-exporter": "prometheus-borg-exporter", 988 "prometheus-borg-exporter": "prometheus-borg-exporter",
989 "pyproject-build-systems": "pyproject-build-systems_2",
990 "pyproject-nix": "pyproject-nix",
883 "sops-nix": "sops-nix", 991 "sops-nix": "sops-nix",
992 "uv2nix": "uv2nix",
884 "waybar": "waybar" 993 "waybar": "waybar"
885 } 994 }
886 }, 995 },
@@ -891,11 +1000,11 @@
891 ] 1000 ]
892 }, 1001 },
893 "locked": { 1002 "locked": {
894 "lastModified": 1737411508, 1003 "lastModified": 1751606940,
895 "narHash": "sha256-j9IdflJwRtqo9WpM0OfAZml47eBblUHGNQTe62OUqTw=", 1004 "narHash": "sha256-KrDPXobG7DFKTOteqdSVeL1bMVitDcy7otpVZWDE6MA=",
896 "owner": "Mic92", 1005 "owner": "Mic92",
897 "repo": "sops-nix", 1006 "repo": "sops-nix",
898 "rev": "015d461c16678fc02a2f405eb453abb509d4e1d4", 1007 "rev": "3633fc4acf03f43b260244d94c71e9e14a2f6e0d",
899 "type": "github" 1008 "type": "github"
900 }, 1009 },
901 "original": { 1010 "original": {
@@ -956,6 +1065,29 @@
956 "type": "github" 1065 "type": "github"
957 } 1066 }
958 }, 1067 },
1068 "uv2nix": {
1069 "inputs": {
1070 "nixpkgs": [
1071 "nixpkgs"
1072 ],
1073 "pyproject-nix": [
1074 "pyproject-nix"
1075 ]
1076 },
1077 "locked": {
1078 "lastModified": 1752195249,
1079 "narHash": "sha256-xJ4P6Ekm1tQTtUHboca+vqosXQZHPCDTYOq/HfZ6o1M=",
1080 "owner": "pyproject-nix",
1081 "repo": "uv2nix",
1082 "rev": "06b039bb2fa0bc57ac6e611e05de70d36bb1af8b",
1083 "type": "github"
1084 },
1085 "original": {
1086 "owner": "pyproject-nix",
1087 "repo": "uv2nix",
1088 "type": "github"
1089 }
1090 },
959 "waybar": { 1091 "waybar": {
960 "inputs": { 1092 "inputs": {
961 "flake-compat": [ 1093 "flake-compat": [
@@ -966,16 +1098,16 @@
966 ] 1098 ]
967 }, 1099 },
968 "locked": { 1100 "locked": {
969 "lastModified": 1737014022, 1101 "lastModified": 1752562190,
970 "narHash": "sha256-5cG3lbjvrqvotI3oEPham3jGq8Fd96NfrqCGvC1e6Qw=", 1102 "narHash": "sha256-zWOMCNe56H2PHUd3rJZ6tklZUZBLgRo85jd9IlK1g9o=",
971 "owner": "gkleen", 1103 "owner": "gkleen",
972 "repo": "Waybar", 1104 "repo": "Waybar",
973 "rev": "83765e0f8e99a7d344eae511a4090a76a27e5791", 1105 "rev": "d008cd998369c40f2344a856caf39cdbbd7bd068",
974 "type": "github" 1106 "type": "github"
975 }, 1107 },
976 "original": { 1108 "original": {
977 "owner": "gkleen", 1109 "owner": "gkleen",
978 "ref": "feat/niri-workspaces-hide", 1110 "ref": "feat/niri-urgency",
979 "repo": "Waybar", 1111 "repo": "Waybar",
980 "type": "github" 1112 "type": "github"
981 } 1113 }
@@ -983,16 +1115,16 @@
983 "xwayland-satellite-stable": { 1115 "xwayland-satellite-stable": {
984 "flake": false, 1116 "flake": false,
985 "locked": { 1117 "locked": {
986 "lastModified": 1730166465, 1118 "lastModified": 1748488455,
987 "narHash": "sha256-nq7bouXQXaaPPo/E+Jbq+wNHnatD4dY8OxSrRqzvy6s=", 1119 "narHash": "sha256-IiLr1alzKFIy5tGGpDlabQbe6LV1c9ABvkH6T5WmyRI=",
988 "owner": "Supreeeme", 1120 "owner": "Supreeeme",
989 "repo": "xwayland-satellite", 1121 "repo": "xwayland-satellite",
990 "rev": "a713cf46cb7db84a0d1b57c3a397c610cad3cf98", 1122 "rev": "3ba30b149f9eb2bbf42cf4758d2158ca8cceef73",
991 "type": "github" 1123 "type": "github"
992 }, 1124 },
993 "original": { 1125 "original": {
994 "owner": "Supreeeme", 1126 "owner": "Supreeeme",
995 "ref": "v0.5", 1127 "ref": "v0.6",
996 "repo": "xwayland-satellite", 1128 "repo": "xwayland-satellite",
997 "type": "github" 1129 "type": "github"
998 } 1130 }
@@ -1000,11 +1132,11 @@
1000 "xwayland-satellite-unstable": { 1132 "xwayland-satellite-unstable": {
1001 "flake": false, 1133 "flake": false,
1002 "locked": { 1134 "locked": {
1003 "lastModified": 1737837494, 1135 "lastModified": 1751228685,
1004 "narHash": "sha256-wIMowP8Juas4ZwMRcpc+58sZ0kKTDu8fm13THPmv/F8=", 1136 "narHash": "sha256-MENtauGBhJ+kDeFaawvWGXaFG3Il6qQzjaP0RmtfM0k=",
1005 "owner": "Supreeeme", 1137 "owner": "Supreeeme",
1006 "repo": "xwayland-satellite", 1138 "repo": "xwayland-satellite",
1007 "rev": "3944c9a0e40e5629f16ad023bbc90dac80d35a0f", 1139 "rev": "557ebeb616e03d5e4a8049862bbbd1f02c6f020b",
1008 "type": "github" 1140 "type": "github"
1009 }, 1141 },
1010 "original": { 1142 "original": {
diff --git a/flake.nix b/flake.nix
index 5cc1e298..8380f9c7 100644
--- a/flake.nix
+++ b/flake.nix
@@ -29,13 +29,13 @@
29 type = "github"; 29 type = "github";
30 owner = "NixOS"; 30 owner = "NixOS";
31 repo = "nixpkgs"; 31 repo = "nixpkgs";
32 ref = "24.05"; 32 ref = "25.05";
33 }; 33 };
34 nixpkgs-eostre = { 34 nixpkgs-eostre = {
35 type = "github"; 35 type = "github";
36 owner = "NixOS"; 36 owner = "NixOS";
37 repo = "nixpkgs"; 37 repo = "nixpkgs";
38 ref = "23.11"; 38 ref = "25.05";
39 }; 39 };
40 home-manager = { 40 home-manager = {
41 type = "github"; 41 type = "github";
@@ -53,7 +53,7 @@
53 type = "github"; 53 type = "github";
54 owner = "gkleen"; 54 owner = "gkleen";
55 repo = "home-manager"; 55 repo = "home-manager";
56 ref = "nixos-late-start-23.11"; 56 ref = "nixos-late-start-25.05";
57 inputs = { 57 inputs = {
58 nixpkgs.follows = "nixpkgs-eostre"; 58 nixpkgs.follows = "nixpkgs-eostre";
59 }; 59 };
@@ -125,25 +125,43 @@
125 nixpkgs.follows = "nixpkgs"; 125 nixpkgs.follows = "nixpkgs";
126 }; 126 };
127 }; 127 };
128 pyproject-nix = {
129 url = "github:pyproject-nix/pyproject.nix";
130 inputs.nixpkgs.follows = "nixpkgs";
131 };
132 uv2nix = {
133 url = "github:pyproject-nix/uv2nix";
134 inputs.pyproject-nix.follows = "pyproject-nix";
135 inputs.nixpkgs.follows = "nixpkgs";
136 };
137 pyproject-build-systems = {
138 url = "github:pyproject-nix/build-system-pkgs";
139 inputs.pyproject-nix.follows = "pyproject-nix";
140 inputs.uv2nix.follows = "uv2nix";
141 inputs.nixpkgs.follows = "nixpkgs";
142 };
128 143
129 ca-util = { 144 ca-util = {
130 type = "gitlab"; 145 type = "gitlab";
131 owner = "gkleen"; 146 owner = "gkleen";
132 repo = "ca"; 147 repo = "ca";
133 ref = "v3.1.3"; 148 ref = "v3.1.5";
134 inputs = { 149 inputs = {
150 pyproject-nix.follows = "pyproject-nix";
151 uv2nix.follows = "uv2nix";
135 nixpkgs.follows = "nixpkgs"; 152 nixpkgs.follows = "nixpkgs";
136 poetry2nix.follows = "poetry2nix";
137 }; 153 };
138 }; 154 };
139 backup-utils = { 155 backup-utils = {
140 type = "gitlab"; 156 type = "gitlab";
141 owner = "gkleen"; 157 owner = "gkleen";
142 repo = "backup-utils"; 158 repo = "backup-utils";
143 ref = "v0.1.6"; 159 ref = "v0.1.7";
144 inputs = { 160 inputs = {
145 nixpkgs.follows = "nixpkgs"; 161 nixpkgs.follows = "nixpkgs";
146 poetry2nix.follows = "poetry2nix"; 162 pyproject-nix.follows = "pyproject-nix";
163 uv2nix.follows = "uv2nix";
164 pyproject-build-systems.follows = "pyproject-build-systems";
147 }; 165 };
148 }; 166 };
149 prometheus-borg-exporter = { 167 prometheus-borg-exporter = {
@@ -172,7 +190,7 @@
172 type = "github"; 190 type = "github";
173 owner = "gkleen"; 191 owner = "gkleen";
174 repo = "Waybar"; 192 repo = "Waybar";
175 ref = "feat/niri-workspaces-hide"; 193 ref = "feat/niri-urgency";
176 inputs = { 194 inputs = {
177 nixpkgs.follows = "nixpkgs"; 195 nixpkgs.follows = "nixpkgs";
178 flake-compat.follows = "flake-compat"; 196 flake-compat.follows = "flake-compat";
@@ -191,7 +209,16 @@
191 ref = "main"; 209 ref = "main";
192 inputs = { 210 inputs = {
193 nixpkgs.follows = "nixpkgs"; 211 nixpkgs.follows = "nixpkgs";
194 niri-unstable.url = "github:gkleen/niri"; 212 # niri-unstable.url = "github:gkleen/niri";
213 };
214 };
215 nix-monitored = {
216 type = "github";
217 owner = "ners";
218 repo = "nix-monitored";
219 ref = "master";
220 inputs = {
221 nixpkgs.follows = "nixpkgs";
195 }; 222 };
196 }; 223 };
197 }; 224 };
@@ -298,7 +325,7 @@
298 forAllUsers = genAttrs (unique (map accountUserName (attrNames self.nixosModules.accounts))); 325 forAllUsers = genAttrs (unique (map accountUserName (attrNames self.nixosModules.accounts)));
299 326
300 activateNixosConfigurations = forAllSystems (system: _pkgs: filterAttrs (_n: v: v != null) (mapAttrs' (hostName: nixosConfig: nameValuePair "${hostName}-activate" (if system == nixosConfig.config.nixpkgs.system then { type = "app"; program = "${nixosConfig.config.system.build.toplevel}/bin/switch-to-configuration"; } else null)) self.nixosConfigurations)); 327 activateNixosConfigurations = forAllSystems (system: _pkgs: filterAttrs (_n: v: v != null) (mapAttrs' (hostName: nixosConfig: nameValuePair "${hostName}-activate" (if system == nixosConfig.config.nixpkgs.system then { type = "app"; program = "${nixosConfig.config.system.build.toplevel}/bin/switch-to-configuration"; } else null)) self.nixosConfigurations));
301 startVMs = forAllSystems (system: pkgs: mapAttrs' (hostName: nixosConfig: nameValuePair "run-${hostName}-vm" { type = "app"; program = "${nixosConfig.config.system.build.vm}/bin/run-${hostName}-vm"; }) (nixImport rec { dir = ./hosts; _import = mkNixosConfiguration [ { config.virtualisation.host.pkgs = pkgs; } ] dir; })); 328 # startVMs = forAllSystems (system: pkgs: mapAttrs' (hostName: nixosConfig: nameValuePair "run-${hostName}-vm" { type = "app"; program = "${nixosConfig.config.system.build.vm}/bin/run-${hostName}-vm"; }) (nixImport rec { dir = ./hosts; _import = mkNixosConfiguration [ { config.virtualisation.host.pkgs = pkgs; } ] dir; }));
302 activateHomeManagerConfigurations = forAllSystems (system: _pkgs: filterAttrs (_n: v: v != null) (listToAttrs (concatLists (mapAttrsToList (hostName: nixosConfig: mapAttrsToList (userName: userCfg: nameValuePair "${userName}@${hostName}-activate" (if system == nixosConfig.config.nixpkgs.system then { type = "app"; program = "${userCfg.home.activationPackage}/activate"; } else null)) nixosConfig.config.home-manager.users) self.nixosConfigurations)))); 329 activateHomeManagerConfigurations = forAllSystems (system: _pkgs: filterAttrs (_n: v: v != null) (listToAttrs (concatLists (mapAttrsToList (hostName: nixosConfig: mapAttrsToList (userName: userCfg: nameValuePair "${userName}@${hostName}-activate" (if system == nixosConfig.config.nixpkgs.system then { type = "app"; program = "${userCfg.home.activationPackage}/activate"; } else null)) nixosConfig.config.home-manager.users) self.nixosConfigurations))));
303 installerShells = system: pkgs: mapAttrs (installerName: config: pkgs.callPackage ./installer/shell.nix { 330 installerShells = system: pkgs: mapAttrs (installerName: config: pkgs.callPackage ./installer/shell.nix {
304 inherit system installerName config; 331 inherit system installerName config;
@@ -339,14 +366,19 @@
339 366
340 overlays = mapAttrs (_name: path: mkOverlay path) overlayPaths; 367 overlays = mapAttrs (_name: path: mkOverlay path) overlayPaths;
341 368
342 packages = forAllSystems (system: systemPkgs: nixImport rec { dir = ./tools; _import = _path: name: import "${toString dir}/${name}" ({ inherit system; } // inputs); }); 369 packages = forAllSystems (system: systemPkgs: nixImport rec { dir = ./tools; _import = name: _base: import (dir + "/${name}") ({ inherit system; } // inputs); });
343 370
344 # packages = mapAttrs (_name: filterAttrs (_name: isDerivation)) packages; 371 # packages = mapAttrs (_name: filterAttrs (_name: isDerivation)) packages;
345 # packages' = mapAttrs (_name: filterAttrs (_name: value: !(isDerivation value))) packages; 372 # packages' = mapAttrs (_name: filterAttrs (_name: value: !(isDerivation value))) packages;
346 373
347 legacyPackages = forAllSystems (system: systemPkgs: systemPkgs.override { overlays = attrValues self.overlays; }); 374 legacyPackages = forAllSystems (system: systemPkgs: systemPkgs.override { overlays = attrValues self.overlays; });
348 375
349 apps = foldr recursiveUpdate {} [startVMs activateNixosConfigurations activateHomeManagerConfigurations]; 376 apps = foldr recursiveUpdate {} [
377 #startVMs
378 activateNixosConfigurations activateHomeManagerConfigurations
379 ];
380
381 lib = nixImport rec { dir = ./lib; _import = name: _base: import (dir + "/${name}") inputs; };
350 382
351 devShells = forAllSystems (system: systemPkgs: { default = import ./shell.nix ({ inherit system; } // inputs); } // installerShells system systemPkgs); 383 devShells = forAllSystems (system: systemPkgs: { default = import ./shell.nix ({ inherit system; } // inputs); } // installerShells system systemPkgs);
352 384
@@ -371,10 +403,10 @@
371 # path = activateHomeManager (self.nixosConfigurations.${hostname}.config.nixpkgs.system) usercfg.home; 403 # path = activateHomeManager (self.nixosConfigurations.${hostname}.config.nixpkgs.system) usercfg.home;
372 # }) self.nixosConfigurations.${hostname}.config.home-manager.users); 404 # }) self.nixosConfigurations.${hostname}.config.home-manager.users);
373 }) (nixImport { dir = ./hosts; _import = (_path: name: name); }); 405 }) (nixImport { dir = ./hosts; _import = (_path: name: name); });
374 overrides = if pathExists ./deploy then nixImport { dir = ./deploy; _import = path: _name: import (./deploy + "/${path}") inputs; } else {}; 406 overrides = if pathExists ./deploy then nixImport rec { dir = ./deploy; _import = path: _name: import (dir + "/${path}") inputs; } else {};
375 filterEnabled = attrs: mapAttrs (_n: v: filterAttrs (n: _v: n != "enabled") v) (filterAttrs (_n: v: v.enabled or true) attrs); 407 filterEnabled = attrs: mapAttrs (_n: v: filterAttrs (n: _v: n != "enabled") v) (filterAttrs (_n: v: v.enabled or true) attrs);
376 in mapAttrs (_n: v: if v ? "profiles" then v // { profiles = filterEnabled v.profiles; } else v) (filterEnabled (recursiveUpdate defaults overrides)); 408 in mapAttrs (_n: v: if v ? "profiles" then v // { profiles = filterEnabled v.profiles; } else v) (filterEnabled (recursiveUpdate defaults overrides));
377 409
378 checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks self.deploy) deploy-rs.lib; 410 # checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks self.deploy) deploy-rs.lib;
379 }; 411 };
380} 412}
diff --git a/home-modules/nixpkgs-release-check.nix b/home-modules/nixpkgs-release-check.nix
new file mode 100644
index 00000000..baf2713a
--- /dev/null
+++ b/home-modules/nixpkgs-release-check.nix
@@ -0,0 +1,4 @@
1{ ... }:
2{
3 config.home.enableNixpkgsReleaseCheck = false;
4}
diff --git a/home-modules/pandoc/default.nix b/home-modules/pandoc/default.nix
new file mode 100644
index 00000000..1d16b621
--- /dev/null
+++ b/home-modules/pandoc/default.nix
@@ -0,0 +1,27 @@
1{ pkgs, lib, config, ... }:
2
3let
4 cfg = config.programs.pandoc;
5in {
6 options.programs.pandoc = {
7 germanAbbreviations = lib.mkEnableOption "importing german abbreviations" // { default = true; };
8 extraAbbreviations = lib.mkOption {
9 type = lib.types.listOf lib.types.str;
10 default = [];
11 };
12 };
13
14 config = lib.mkIf cfg.enable {
15 xdg.dataFile = lib.mkIf (cfg.germanAbbreviations || cfg.extraAbbreviations != []) {
16 "pandoc/abbreviations".source = pkgs.runCommand "pandoc-abbreviations" {
17 buildInputs = [ pkgs.coreutils ];
18 } ''
19 cat \
20 <(${lib.getExe' cfg.finalPackage "pandoc"} --print-default-data-file=abbreviations) \
21 ${lib.optionalString cfg.germanAbbreviations ./german_abbreviations.txt} \
22 ${lib.optionalString (cfg.extraAbbreviations != []) (pkgs.writeText "abbrevs.txt" (lib.concatStringsSep "\n" cfg.extraAbbreviations))} \
23 | sort | uniq >$out
24 '';
25 };
26 };
27}
diff --git a/home-modules/pandoc/german_abbreviations.txt b/home-modules/pandoc/german_abbreviations.txt
new file mode 100644
index 00000000..fa4c9c87
--- /dev/null
+++ b/home-modules/pandoc/german_abbreviations.txt
@@ -0,0 +1,1423 @@
1&c.
2A.
3a.
4a.a.O.
5A.C.A.B.
6a.D.
7a.d.D.
8a.g.O.
9Abb.
10abchas.
11abds.
12Abf.
13Abfr.
14Abg.
15abgek.
16abh.
17Abh.
18Abk.
19ABl.
20Abl.
21Abm.
22abn.
23Abn.
24abr.
25Abr.
26Abs.
27abs.
28Abschn.
29Abst.
30Abt.
31abulg.
32abw.
33abwert.
34abzgl.
35accel.
36accresc.
37Add.
38Adj.
39adj.
40Adr.
41adv.
42Adv.
43adyg.
44ae.
45aengl.
46afghan.
47afr.
48afranz.
49afranzös.
50afries.
51afrik.
52afrk.
53afrs.
54afrz.
55afränk.
56ags.
57ahd.
58Ahd.
59aind.
60air.
61akad.
62Akk.
63akkad.
64akt.
65alb.
66alban.
67alem.
68alemann.
69all.
70allg.
71allj.
72allm.
73alltagsspr.
74alphanum.
75Alt.
76altai.
77altengl.
78altfranz.
79altfranzös.
80altfrz.
81altgr.
82althochdt.
83altis.
84altisländ.
85altpreuß.
86altröm.
87alttest.
88alëut.
89am.
90amer.
91amerik.
92amerikan.
93amhar.
94amt.
95amtl.
96Amtm.
97Amtsbl.
98Amtsdt.
99Amtsspr.
100an.
101anal.
102anat.
103Anat.
104anatom.
105andalus.
106ang.
107angelsächs.
108Angest.
109angest.
110angloamerik.
111anglofrz.
112angloind.
113Anh.
114Ank.
115Ankl.
116Anl.
117anl.
118Anm.
119Anm.d.Red.
120Ann.
121ann.
122annamit.
123anord.
124Anord.
125anschl.
126Anschl.
127Anschr.
128antarkt.
129Anthrop.
130anthrop.
131Anw.
132aobd.
133apl.
134Apostr.
135App.
136Apr.
137apreuß.
138ar.
139arab.
140aragon.
141aram.
142aran.
143architekt.
144archäol.
145arg.
146argent.
147arkt.
148armen.
149Art.
150Art.-Nr.
151Artt.
152as.
153aserbaidsch.
154aslaw.
155assyr.
156astron.
157asächs.
158At.-Gew.
159attr.
160Attr.
161Aufl.
162Aug.
163Ausg.
164ausgen.
165Aussch.
166ausschl.
167Ausspr.
168Ausst.
169austral.
170awar.
171awest.
172Az.
173aztek.
174b.
175B.
176Ba.-Wü.
177bab.
178babyl.
179bair.
180Bakt.
181Bal.
182balt.
183baltoslaw.
184Bankw.
185bas.
186baschk.
187bask.
188Bat.
189bauf.
190Bauw.
191bay.
192bayer.
193bayr.
194BayVBl.
195Bd.
196Bde.
197Bed.
198Begr.
199begr.
200beif.
201beil.
202Beil.
203Bem.
204ben.
205berbersprachl.
206Bergb.
207berlin.
208Berufsbez.
209bes.
210besch.
211Beschl.
212best.
213Best.-Nr.
214Betr.
215betr.
216Betriebswiss.
217Bev.
218Bez.
219bez.
220bezw.
221Bf.
222bfn.
223Bg.
224bgld.
225Bgld.
226Bhf.
227Bib.
228bibl.
229bildl.
230bildungsspr.
231Biol.
232biol.
233Bj.
234bl.
235Bl.
236Blk.
237Bln.
238Bodenk.
239bot.
240Bot.
241Br.-M.
242Br.-Mstr.
243bras.
244bret.
245breton.
246brit.
247Brm.
248brn.
249Bruchz.
250bsd.
251Bsp.
252bsplsw.
253bspw.
254BT-Drs.
255Btl.
256btto.
257Bttr.
258Buchw.
259buddh.
260bulg.
261bulgar.
262burjat.
263burmes.
264Bw.
265byzant.
266Bz.
267bzb.
268bzgl.
269bzw.
270böhm.
271Börsenw.
272C.
273ca.
274Carp.
275Cb.
276cf.
277chakass.
278chald.
279chant.
280chem.
281Chem.
282chilen.
283chin.
284Chr.
285christl.
286chron.
287Chron.
288Co.
289Comp.
290cresc.
291D.
292Dankb.
293dankwtw.
294das.
295dass.
296Dat.
297dbzgl.
298ders.
299des.
300desgl.
301Dez.
302dgl.
303Di.
304dial.
305dichter.
306dies.
307dim.
308Dim.
309Dimin.
310dimin.
311Dipl.
312Dipl.-Bibl.
313Dipl.-Ing.
314Dipl.-Kff.
315Dipl.-Kffr.
316Dipl.-Kfm.
317Dipl.-Kfr.
318Dipl.-Psych.
319Dir.
320Diss.
321Do.
322do.
323Do.-Gge.
324dominikan.
325dor.
326Doz.
327Dr.
328Drchf.
329Drcks.
330Dres.
331Drs.
332Drucks.
333dt.
334Dtl.
335dto.
336Dtzd.
337dz.
338Dz.
339dän.
340E.
341ebd.
342Ed.
343ed.
344ehem.
345eidg.
346eig.
347eigtl.
348Einf.
349einh.
350Einl.
351einschl.
352Einw.
353Eisenb.
354Elektrot.
355elektrotechn.
356em.
357engl.
358entspr.
359erb.
360erf.
361erg.
362Erg.
363erk.
364Erl.
365erm.
366Ers.-D.
367ersch.
368erschl.
369Erschl.
370Erschl.-Geb.
371Erschw.
372erschw.
373Erstauff.
374Erstausg.
375Ertr.
376Erw.
377Erw.-Bldg.
378erwähnw.
379Erzb.
380erzg.
381erzgeb.
382eskim.
383estn.
384etc.
385Etg.
386etrusk.
387etw.
388eur.
389europ.
390ev.
391evang.
392evtl.
393Ew.
394ewen.
395ewenk.
396exkl.
397Expl.
398Ez.
399f.
400F.
401Fa.
402fachspr.
403Fachspr.
404Fag.
405Fam.
406fam.
407Febr.
408fem.
409ff.
410Fig.
411fig.
412finanzmath.
413finn.
414finnougr.
415Flgh.
416fläm.
417Fn.
418fnhd.
419folg.
420Forts.
421Fortstzg.
422Fr.
423fr.
424fragm.
425franz.
426französ.
427Frdf.
428frdl.
429frdsprlg.
430Frfr.
431frfr.
432Frh.
433Frhf.
434Frhr.
435fries.
436friesl.
437Frk.
438Frl.
439Frm.
440frnhd.
441Frspr.
442frstl.
443Frt.
444frtr.
445Frwk.
446frz.
447fränk.
448frühnhd.
449Fs.
450Fsch.
451Fschr.
452Fsm.
453Ftm.
454Fut.
455fut.
456färö.
457förml.
458g.
459Ga.
460gall.
461galloroman.
462Gart.
463gaskogn.
464gbd.
465Gbd.
466Gbf.
467GBl.
468Gbl.
469geb.
470Geb.
471Geb.-T.
472Gebr.
473gebr.
474ged.
475gef.
476geg.
477gegr.
478geh.
479gek.
480gel.
481geleg.
482gem.
483gemeingerm.
484gen.
485Gen.
486geod.
487geogr.
488geograf.
489geograph.
490geol.
491geolog.
492geophys.
493georg.
494gep.
495ger.
496germ.
497Ges.
498ges.
499gesch.
500gespr.
501gest.
502get.
503Gew.
504gew.
505gez.
506Gfsch.
507Gft.
508gg.
509ggb.
510ggbfs.
511ggez.
512ggf.
513ggfls.
514ggfs.
515Ggs.
516ggü.
517Ghzg.
518Ghzgt.
519glchz.
520Gld.
521Glde.
522gldg.
523Gldr.
524Gled.
525gleichbed.
526gleichn.
527gleichz.
528Glfl.
529gls.
530gltd.
531gltg.
532glz.
533gm.
534got.
535gr.
536Gr.
537Gramm.
538grammat.
539graph.
540grch.
541Grchl.
542Grdb.
543Grdf.
544Grdfl.
545Grdg.
546Grdl.
547Grdr.
548grds.
549Grdst.
550griech.
551Grz.
552grönländ.
553Gstb.
554Gt.
555gyn.
556gynäk.
557gäl.
558H.-I.
559H.-Qu.
560hait.
561Handw.
562Hbf.
563hd.
564Hd.-Bibl.
565Hdb.
566hdbr.
567Hdbr.
568hdl.
569Hdl.
570Hdlbg.
571hebr.
572hess.
573hethit.
574Hf.
575Hg.
576hg.
577hindust.
578hinr.
579hins.
580Hinw.
581hist.
582HJber.
583Hkl.
584hl.
585hochd.
586hochspr.
587Hom.
588hor.
589Hpfl.
590hptpl.
591hpts.
592Hptst.
593hptw.
594Hptw.
595HQu.
596Hr.
597HReg.
598Hrn.
599Hrsg.
600hrsg.
601Hs.
602Hs.-Nr.
603hschr.
604Hschr.
605HSt.
606Hubbr.
607Hubr.
608Hw.
609Hyaz.
610hydr.
611hydrol.
612Hzm.
613i.
614I.E.
615i.g.O.
616i.Tr.
617iber.
618ibid.
619ide.
620Ident.
621ident.
622idg.
623ie.
624illyr.
625Imkerspr.
626imp.
627Imp.
628in.
629Ind.
630ind.
631indef.
632indekl.
633indian.
634indiff.
635indir.
636indiv.
637indog.
638indogerm.
639indogerman.
640indoiran.
641indon.
642indones.
643Inf.
644inf.
645Ing.
646Inh.
647inkl.
648inn.
649Ins.
650insb.
651insbes.
652int.
653intern.
654intrans.
655ir.
656iran.
657iron.
658isl.
659islam.
660isländ.
661it.
662ital.
663italien.
664j.
665J.
666Jahrh.
667jakut.
668Jan.
669jap.
670japan.
671jav.
672jem.
673jemen.
674Jg.
675jgdfr.
676Jh.
677Jhd.
678Jhdt.
679Jhg.
680Jhs.
681jidd.
682jmd.
683jmdm.
684jmdn.
685jmds.
686journ.
687jr.
688Jr.
689Jt.
690Jtsd.
691jugendspr.
692jugendsprachl.
693jugoslaw.
694Jul.
695jun.
696Jun.
697jur.
698Juw.
699jägersprachl.
700jährl.
701Jän.
702jüd.
703k.u.k.
704K.Ö.St.V.
705kalm.
706kanad.
707Kap.
708karib.
709kastil.
710katal.
711katalan.
712kath.
713kaufm.
714kaukas.
715kelt.
716Kgr.
717Kh.
718kindersprachl.
719kirchenlat.
720kirchenslaw.
721kirchl.
722kirg.
723Kl.
724klass.
725klass.-lat.
726klimatol.
727kol.
728Komm.
729Konj.
730Konv.
731Kop.
732kop.
733kopt.
734korean.
735Kr.
736kreol.
737kret.
738Krh.
739Krhs.
740Krim.-Ob.-Insp.
741krimgot.
742kriminaltechn.
743Krkhs.
744kroat.
745Krs.
746Ks.
747Kto.
748Kto.-Nr.
749kuban.
750kurd.
751Kurzw.
752Kw.
753l.
754L.-Abg.
755lab.
756LAbg.
757ladin.
758landsch.
759Landw.
760langfr.
761langj.
762langob.
763langobard.
764lapp.
765lat.
766latein.
767latinis.
768lautl.
769lautm.
770lbd.
771lbdg.
772Ldkr.
773led.
774leg.
775lett.
776lfd.
777Lfg.
778Lfm.
779Lfrg.
780Lg.
781lgfr.
782Lgft.
783lgj.
784lig.
785ling.
786lit.
787LL.M.
788lrh.
789lt.
790ltd.
791luth.
792luxemb.
793Lz.
794m.
795M.
796M.-Schr.
797m.a.W.
798ma.
799MA.
800Mag.
801malai.
802marinespr.
803marx.
804mask.
805math.
806Math.
807max.
808Max.
809mazedon.
810mbl.
811Mbl.
812MBl.
813Mbll.
814md.
815mdal.
816mdj.
817mdl.
818mdls.
819Mdt.
820me.
821mech.
822meckl.
823med.
824melanes.
825mengl.
826Merc.
827met.
828meteorol.
829meton.
830mex.
831mexik.
832mfr.
833mfranz.
834mfrk.
835mfrz.
836mfränk.
837mgl.
838Mgl.
839mglw.
840mhd.
841mhdt.
842Mi.
843mi.
844Mia.
845milit.
846Mill.
847min.
848Min.
849mind.
850Mio.
851mir.
852Mitgl.
853mitteld.
854mitteldt.
855mittelhochdt.
856Mittw.
857Mitw.
858mlat.
859Mme.
860Mmes.
861mnd.
862mndd.
863mniederd.
864mnl.
865Mo.
866mod.
867mong.
868Mrd.
869Mrz.
870Mschr.
871Msgr.
872Msp.
873mtl.
874mundartl.
875musik.
876MwSt.
877Myth.
878Mz.
879männl.
880möbl.
881n.
882Nachf.
883nachm.
884nat.
885nationalsoz.
886natsoz.
887Nbf.
888Nbfl.
889Nchf.
890nd.
891ndd.
892ndrl.
893neapolit.
894Neub.
895neunorweg.
896neutest.
897neutr.
898Nfl.
899ngl.
900ngr.
901nhbr.
902nhd.
903nicar.
904niederd.
905niederdt.
906niederl.
907niederld.
908niem.
909niger.
910nihil.
911nl.
912nlat.
913nmtl.
914Nom.
915nord.
916nordamerik.
917nordd.
918norddt.
919nordgerm.
920nordostd.
921nordostdt.
922nordwestd.
923nordwestdt.
924norm.
925norw.
926norweg.
927Nov.
928Nr.
929ntw.
930Ntw.
931Nutzfl.
932nw.
933näml.
934nö.
935nördl.
936o.
937O.K.
938ob.
939Ob.
940Obb.
941obb.
942obd.
943Oberlaus.
944obers.
945obersächs.
946obj.
947od.
948offiz.
949Offz.
950Ofr.
951ofrs.
952Okt.
953op.
954Orch.-Bes.
955org.
956Orig.
957orn.
958orth.
959Ortskl.
960Osch.
961osk.
962osman.
963ostd.
964ostdt.
965ostfr.
966ostfrz.
967ostgerm.
968ostidg.
969ostmdt.
970ostmitteld.
971ostniederd.
972ostpr.
973ostpreuß.
974ostw.
975osö.
976Ouv.
977oz.
978Oz.
979oö.
980OÖ.
981P.
982p.
983P.S.
984pa.
985palästin.
986par.
987parag.
988Paragr.
989Parl.
990Part.
991pass.
992Pat.
993pej.
994pers.
995peruan.
996Pet.
997Pf.
998Pfd.
999Pfg.
1000Pfl.
1001pharm.
1002philos.
1003Philos.
1004phonolog.
1005phryg.
1006Phys.
1007phys.
1008phöniz.
1009Pi.
1010pik.
1011Pkt.
1012Pl.
1013Plur.
1014poet.
1015Pol.
1016pol.
1017polit.
1018poln.
1019polynes.
1020port.
1021portug.
1022Pos.
1023pos.
1024pp.
1025ppa.
1026preuß.
1027Priv.-Doz.
1028Prof.
1029prot.
1030Prot.
1031prov.
1032Prov.
1033prov.-fr.
1034provenz.
1035Proz.
1036Proz.-Bev.
1037präd.
1038prähist.
1039Präs.
1040Psych.
1041psych.
1042Päd.
1043Q.
1044q.v.
1045Qmstr.
1046Qt.
1047qu.
1048Qu.
1049quadr.
1050Quadr.
1051qual.
1052Qual.
1053quant.
1054Quant.
1055Quar.
1056Quart.
1057Quat.
1058quitt.
1059Quitt.
1060Quäst.
1061r.
1062r.-k.
1063Rab.
1064rad.
1065Raff.
1066Rak.
1067Randb.
1068Randbem.
1069rat.
1070Rat.
1071Rb.
1072rd.
1073RdErl.
1074Rdf.
1075refl.
1076Reg.
1077Reg.-Bez.
1078Regt.
1079Rel.
1080rel.
1081relig.
1082Rep.
1083resp.
1084Rg.-Präs.
1085RGBl.
1086rglm.
1087Rgstr.
1088Rgt.
1089Rh.
1090rh.
1091rhein.
1092rheinhess.
1093rhet.
1094rhfrk.
1095Rhj.
1096Rhld.
1097Rhs.
1098Ri.
1099Richtl.
1100rip.
1101rk.
1102roman.
1103rotw.
1104Rr.
1105rrh.
1106Rspr.
1107Rtn.
1108Rtt.
1109rumän.
1110russ.
1111Rvj.
1112rzp.
1113rätorom.
1114röm.
1115röm.-kath.
1116S.
1117s.
1118S.-Wk.
1119Sa.
1120Sachs.
1121san.
1122sanskr.
1123Sat.
1124sat.
1125Sb.
1126Sbd.
1127sc.
1128scherzh.
1129Schill.
1130schles.
1131schott.
1132schr.
1133schriftl.
1134Schussw.
1135schwed.
1136schweiz.
1137Schwg.
1138Schwp.
1139schwäb.
1140scil.
1141Sdp.
1142sek.
1143sem.
1144semit.
1145sen.
1146Sep.
1147Sept.
1148serb.
1149serbokroat.
1150Sg.
1151sibir.
1152Sing.
1153singhal.
1154Sir.
1155sizilian.
1156skand.
1157slaw.
1158slow.
1159slowak.
1160slowen.
1161So.
1162sod.
1163sof.
1164sog.
1165sogen.
1166sogl.
1167soldatenspr.
1168solv.
1169somal.
1170sorb.
1171Sout.
1172soz.
1173soziol.
1174span.
1175spez.
1176sportspr.
1177Spr.
1178sprachwiss.
1179Spvg.
1180Spvgg.
1181spätahd.
1182spätgriech.
1183spätlat.
1184spätmhd.
1185Sr.
1186ssp.
1187St.
1188St.-Nr.
1189staatl.
1190Std.
1191stdl.
1192stellv.
1193Stellv.
1194Stk.
1195Str.
1196str.
1197Stud.
1198stud.
1199subsp.
1200Subst.
1201sumer.
1202svw.
1203Swk.
1204syn.
1205Syn.
1206syr.
1207sächs.
1208südafrik.
1209südd.
1210süddt.
1211südl.
1212südostdt.
1213südwestd.
1214Süßw.
1215Tab.
1216Tabl.
1217Taf.
1218tamil.
1219tatar.
1220techn.
1221Tel.
1222telef.
1223Temp.
1224Terr.
1225tessin.
1226test.
1227Tfx.
1228tgl.
1229Tgt.
1230thrak.
1231thür.
1232thüring.
1233Ti.
1234tib.
1235tirol.
1236Tlr.
1237tochar.
1238trans.
1239tsch.
1240tschech.
1241tschechoslowak.
1242Tsd.
1243tun.
1244Tun.
1245tunes.
1246Tunes.
1247tungus.
1248turkotat.
1249typogr.
1250tägl.
1251türk.
1252u.
1253u.a.
1254Ubr.
1255ue.
1256ugr.
1257ugs.
1258ukrain.
1259umbr.
1260umg.
1261unang.
1262unbefl.
1263Unf.
1264unf.
1265unfol.
1266unfr.
1267ung.
1268ungar.
1269ungebr.
1270ungel.
1271ungen.
1272unges.
1273ungl.
1274Uni-Kl.
1275Univ.
1276unv.
1277unverantw.
1278unverb.
1279unverbr.
1280unverd.
1281unverg.
1282unverh.
1283unverk.
1284unverp.
1285unversch.
1286unverz.
1287unverzgl.
1288unvollst.
1289unvorb.
1290unvors.
1291unzerbr.
1292urgerm.
1293urkdl.
1294urspr.
1295ursprüngl.
1296Urt.
1297usf.
1298USt-IdNr.
1299usw.
1300uvm.
1301v.
1302va.
1303Ver.
1304Verf.
1305Verg.
1306vergl.
1307Vergl.
1308verh.
1309Vers.
1310vers.
1311vert.
1312Vfg.
1313vgbl.
1314vgl.
1315Vgl.
1316vh.
1317viell.
1318vj.
1319Vj.
1320vl.
1321vlat.
1322vlt.
1323vmtl.
1324volkst.
1325Vors.
1326vrstl.
1327vrt.
1328vs.
1329vsl.
1330vt.
1331vulg.
1332vulgärlat.
1333Vwz.
1334vzk.
1335w.
1336W.
1337Wa.
1338wal.
1339wehrtgl.
1340weibl.
1341Weis.
1342weißruss.
1343werkt.
1344westd.
1345westdt.
1346Westf.
1347westfäl.
1348westgerm.
1349westl.
1350westmitteld.
1351westmitteldt.
1352Wf.
1353wf.
1354Wfl.
1355wg.
1356wh.
1357Whg.
1358winzerspr.
1359wirtschaftl.
1360wiss.
1361Wkst.
1362Wkstf.
1363wkts.
1364wld.
1365Wr.
1366Ws.
1367Wtb.
1368Ww.
1369Wwe.
1370Wz.
1371Xerogr.
1372Xerok.
1373Xyl.
1374y.
1375Y.
1376yd.
1377Yd.
1378Yds.
1379yds.
1380Z.
1381z.B.
1382za.
1383Zf.
1384Zgm.
1385zgs.
1386zgst.
1387zgw.
1388Zi.
1389Ziff.
1390zit.
1391Zit.
1392zk.
1393Zk.
1394Zool.
1395zool.
1396Zssg.
1397Zssgn.
1398Ztr.
1399Zub.
1400zur.
1401zus.
1402zw.
1403Zz.
1404zz.
1405zzgl.
1406zzt.
1407ägypt.
1408Ökol.
1409ökol.
1410ökon.
1411ökum.
1412örtl.
1413österr.
1414Österr.
1415östl.
1416übers.
1417übertr.
1418überw.
1419Überw.
1420übl.
1421üblw.
1422Übn.
1423übsch.
diff --git a/home-modules/pandoc/german_abbreviations.txt.gup b/home-modules/pandoc/german_abbreviations.txt.gup
new file mode 100755
index 00000000..abcab1da
--- /dev/null
+++ b/home-modules/pandoc/german_abbreviations.txt.gup
@@ -0,0 +1,35 @@
1#!/usr/bin/env nix
2#!nix shell --impure --expr ``
3#!nix with (import (builtins.getFlake ''nixpkgs'') {});
4#!nix python3.withPackages (ps: with ps; [ requests ])
5#!nix `` --command python3
6
7import requests
8import json
9import sys
10import re
11import subprocess
12
13def wiki_cont(url, params):
14 continue_params = None
15 while True:
16 req_params = params
17 if continue_params is not None:
18 req_params |= continue_params
19 json_data = requests.get(url, req_params).json()
20 if "query" in json_data:
21 yield json_data["query"]
22 if "continue" not in json_data:
23 break
24 else:
25 continue_params = json_data["continue"]
26
27out_re = re.compile(r"[^ ]*[^ 0-9][^ ]*\.")
28
29subprocess.run(["gup", "--always"], check=True)
30
31with open(sys.argv[1], 'w') as out:
32 for query in wiki_cont("https://de.wiktionary.org/w/api.php", {"action": "query", "list": "categorymembers", "cmtitle": "Kategorie:Abkürzung_(Deutsch)", "format": "json"}):
33 for item in map(lambda i: i["title"], query["categorymembers"]):
34 if out_re.fullmatch(item):
35 print(item, file=out)
diff --git a/hosts/eostre/default.nix b/hosts/eostre/default.nix
index fd4b15f2..d4113024 100644
--- a/hosts/eostre/default.nix
+++ b/hosts/eostre/default.nix
@@ -37,14 +37,10 @@ with lib;
37 powerManagement.enable = true; 37 powerManagement.enable = true;
38 }; 38 };
39 39
40 opengl.enable = true; 40 graphics.enable = true;
41 }; 41 };
42 42
43 environment.etc."machine-id".text = "f457b21333f1491e916521151ff5d468";
44
45 networking = { 43 networking = {
46 hostId = "f457b213";
47
48 domain = "lan.yggdrasil"; 44 domain = "lan.yggdrasil";
49 search = [ "lan.yggdrasil" "yggdrasil" ]; 45 search = [ "lan.yggdrasil" "yggdrasil" ];
50 46
@@ -83,19 +79,14 @@ with lib;
83 ]; 79 ];
84 }; 80 };
85 81
86 82 services.displayManager.sddm = {
87 services.xserver = {
88 enable = true; 83 enable = true;
89 displayManager.sddm = { 84 wayland.enable = true;
90 enable = true; 85 settings = {
91 settings = { 86 Users.HideUsers = "gkleen";
92 Users.HideUsers = "gkleen";
93 };
94 }; 87 };
95 desktopManager.plasma5.enable = true;
96
97 videoDrivers = [ "nvidia" ];
98 }; 88 };
89 services.desktopManager.plasma6.enable = true;
99 90
100 91
101 services.openssh = { 92 services.openssh = {
diff --git a/hosts/sif/default.nix b/hosts/sif/default.nix
index 14f08ed5..b0d2fd78 100644
--- a/hosts/sif/default.nix
+++ b/hosts/sif/default.nix
@@ -12,7 +12,7 @@ let
12in { 12in {
13 imports = with flake.nixosModules.systemProfiles; [ 13 imports = with flake.nixosModules.systemProfiles; [
14 ./hw.nix 14 ./hw.nix
15 ./mail ./libvirt ./greetd 15 ./email ./libvirt ./greetd
16 tmpfs-root bcachefs initrd-all-crypto-modules default-locale openssh rebuild-machines niri-unstable networkmanager 16 tmpfs-root bcachefs initrd-all-crypto-modules default-locale openssh rebuild-machines niri-unstable networkmanager
17 flakeInputs.nixos-hardware.nixosModules.lenovo-thinkpad-p1 17 flakeInputs.nixos-hardware.nixosModules.lenovo-thinkpad-p1
18 flakeInputs.impermanence.nixosModules.impermanence 18 flakeInputs.impermanence.nixosModules.impermanence
@@ -26,9 +26,6 @@ in {
26 allowUnfree = true; 26 allowUnfree = true;
27 pulseaudio = true; 27 pulseaudio = true;
28 }; 28 };
29 extraOverlays = [
30 flakeInputs.niri-flake.overlays.niri
31 ];
32 }; 29 };
33 30
34 time.timeZone = null; 31 time.timeZone = null;
@@ -55,6 +52,7 @@ in {
55 systemd-boot = { 52 systemd-boot = {
56 enable = true; 53 enable = true;
57 configurationLimit = 15; 54 configurationLimit = 15;
55 netbootxyz.enable = true;
58 }; 56 };
59 efi.canTouchEfiVariables = true; 57 efi.canTouchEfiVariables = true;
60 timeout = null; 58 timeout = null;
@@ -100,6 +98,8 @@ in {
100 server ptbtime2.ptb.de prefer iburst nts 98 server ptbtime2.ptb.de prefer iburst nts
101 server ptbtime3.ptb.de prefer iburst nts 99 server ptbtime3.ptb.de prefer iburst nts
102 server ptbtime4.ptb.de prefer iburst nts 100 server ptbtime4.ptb.de prefer iburst nts
101 pool ntppool1.time.nl prefer iburst nts
102 pool ntppool2.time.nl prefer iburst nts
103 103
104 authselectmode require 104 authselectmode require
105 minsources 3 105 minsources 3
@@ -128,40 +128,16 @@ in {
128 rulesetFile = ./ruleset.nft; 128 rulesetFile = ./ruleset.nft;
129 }; 129 };
130 130
131 # firewall = {
132 # enable = true;
133 # allowedTCPPorts = [ 22 # ssh
134 # 8000 # quickserve
135 # ];
136 # };
137
138 # wlanInterfaces = {
139 # wlan0 = {
140 # device = "wlp82s0";
141 # };
142 # };
143
144 # bonds = {
145 # "lan" = {
146 # interfaces = [ "wlan0" "enp0s31f6" "dock0" ];
147 # driverOptions = {
148 # miimon = "1000";
149 # mode = "active-backup";
150 # primary_reselect = "always";
151 # };
152 # };
153 # };
154
155 useDHCP = false; 131 useDHCP = false;
156 useNetworkd = true; 132 useNetworkd = true;
157
158 # interfaces."tinc.yggdrasil" = {
159 # virtual = true;
160 # virtualType = config.services.tinc.networks.yggdrasil.interfaceType;
161 # macAddress = "5c:93:21:c3:61:39";
162 # };
163 }; 133 };
164 134
135 environment.etc."NetworkManager/dnsmasq.d/dnssec.conf" = {
136 text = ''
137 conf-file=${pkgs.dnsmasq}/share/dnsmasq/trust-anchors.conf
138 dnssec
139 '';
140 };
165 environment.etc."NetworkManager/dnsmasq.d/libvirt_dnsmasq.conf" = { 141 environment.etc."NetworkManager/dnsmasq.d/libvirt_dnsmasq.conf" = {
166 text = '' 142 text = ''
167 except-interface=virbr0 143 except-interface=virbr0
@@ -404,19 +380,6 @@ in {
404 ]; 380 ];
405 381
406 services = { 382 services = {
407 uucp = {
408 enable = true;
409 nodeName = "sif";
410 remoteNodes = {
411 "ymir" = {
412 publicKeys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG6KNtsCOl5fsZ4rV7udTulGMphJweLBoKapzerWNoLY root@ymir"];
413 hostnames = ["ymir.yggdrasil.li" "ymir.niflheim.yggdrasil"];
414 };
415 };
416
417 defaultCommands = lib.mkForce [];
418 };
419
420 avahi.enable = true; 383 avahi.enable = true;
421 384
422 fwupd.enable = true; 385 fwupd.enable = true;
@@ -435,8 +398,8 @@ in {
435 398
436 logind = { 399 logind = {
437 lidSwitch = "suspend"; 400 lidSwitch = "suspend";
438 lidSwitchDocked = "lock"; 401 lidSwitchDocked = "ignore";
439 lidSwitchExternalPower = "lock"; 402 lidSwitchExternalPower = "ignore";
440 }; 403 };
441 404
442 atd = { 405 atd = {
@@ -472,15 +435,17 @@ in {
472 libinput.enable = true; 435 libinput.enable = true;
473 436
474 envfs.enable = false; 437 envfs.enable = false;
438
439 displayManager.defaultSession = "Niri";
475 }; 440 };
476 441
477 systemd.tmpfiles.settings = { 442 systemd.tmpfiles.settings = {
478 "10-localtime"."/etc/localtime".L.argument = "/.bcachefs/etc/localtime"; 443 "10-localtime"."/etc/localtime".L.argument = "/.bcachefs/etc/localtime";
479 444
480 "10-regreet"."/var/cache/regreet/cache.toml".C.argument = toString ((pkgs.formats.toml {}).generate "cache.toml" { 445 # "10-regreet"."/var/cache/regreet/cache.toml".C.argument = toString ((pkgs.formats.toml {}).generate "cache.toml" {
481 last_user = "gkleen"; 446 # last_user = "gkleen";
482 user_to_last_sess.gkleen = "niri"; 447 # user_to_last_sess.gkleen = "Niri";
483 }); 448 # });
484 }; 449 };
485 450
486 users = { 451 users = {
@@ -589,7 +554,7 @@ in {
589 }; 554 };
590 555
591 nvidia = { 556 nvidia = {
592 open = true; 557 open = false;
593 modesetting.enable = true; 558 modesetting.enable = true;
594 powerManagement.enable = true; 559 powerManagement.enable = true;
595 # prime = { 560 # prime = {
@@ -640,25 +605,6 @@ in {
640 605
641 environment.etc."X11/xorg.conf.d/50-wacom.conf".source = lib.mkForce ./wacom.conf; 606 environment.etc."X11/xorg.conf.d/50-wacom.conf".source = lib.mkForce ./wacom.conf;
642 607
643 systemd.services."ac-plugged" = {
644 description = "Inhibit handling of lid-switch and sleep";
645
646 path = with pkgs; [ systemd coreutils ];
647
648 script = ''
649 exec systemd-inhibit --what=handle-lid-switch --why="AC is connected" --mode=block sleep infinity
650 '';
651
652 serviceConfig = {
653 Type = "simple";
654 };
655 };
656
657 services.udev.extraRules = with pkgs; lib.mkAfter ''
658 SUBSYSTEM=="power_supply", ENV{POWER_SUPPLY_ONLINE}=="0", RUN+="${systemd}/bin/systemctl --no-block stop ac-plugged.service"
659 SUBSYSTEM=="power_supply", ENV{POWER_SUPPLY_ONLINE}=="1", RUN+="${systemd}/bin/systemctl --no-block start ac-plugged.service"
660 '';
661
662 systemd.services."nix-daemon".serviceConfig = { 608 systemd.services."nix-daemon".serviceConfig = {
663 MemoryAccounting = true; 609 MemoryAccounting = true;
664 MemoryHigh = "50%"; 610 MemoryHigh = "50%";
@@ -718,7 +664,7 @@ in {
718 directories = [ 664 directories = [
719 "/nix" 665 "/nix"
720 "/root" 666 "/root"
721 "/home" 667 "/home"
722 "/var/log" 668 "/var/log"
723 "/var/lib/sops-nix" 669 "/var/lib/sops-nix"
724 "/var/lib/nixos" 670 "/var/lib/nixos"
@@ -729,8 +675,6 @@ in {
729 "/var/lib/upower" 675 "/var/lib/upower"
730 "/var/lib/postfix" 676 "/var/lib/postfix"
731 "/etc/NetworkManager/system-connections" 677 "/etc/NetworkManager/system-connections"
732 { directory = "/var/uucp"; user = "uucp"; group = "uucp"; mode = "0700"; }
733 { directory = "/var/spool/uucp"; user = "uucp"; group = "uucp"; mode = "0750"; }
734 ]; 678 ];
735 files = [ 679 files = [
736 ]; 680 ];
@@ -751,10 +695,6 @@ in {
751 695
752 home-manager.sharedModules = [ flakeInputs.nixVirt.homeModules.default ]; 696 home-manager.sharedModules = [ flakeInputs.nixVirt.homeModules.default ];
753 697
754 environment.pathsToLink = [
755 "share/zsh"
756 ];
757
758 system.stateVersion = "24.11"; 698 system.stateVersion = "24.11";
759 }; 699 };
760} 700}
diff --git a/hosts/sif/email/default.nix b/hosts/sif/email/default.nix
new file mode 100644
index 00000000..4eda236e
--- /dev/null
+++ b/hosts/sif/email/default.nix
@@ -0,0 +1,110 @@
1{ config, lib, pkgs, ... }:
2{
3 services.postfix = {
4 enable = true;
5 enableSmtp = false;
6 enableSubmission = false;
7 setSendmail = true;
8 networksStyle = "host";
9 hostname = "sif.midgard.yggdrasil";
10 destination = [];
11 recipientDelimiter = "+";
12 config = {
13 mydomain = "yggdrasil.li";
14
15 local_transport = "error:5.1.1 No local delivery";
16 alias_database = [];
17 alias_maps = [];
18 local_recipient_maps = [];
19
20 inet_interfaces = "loopback-only";
21
22 message_size_limit = "0";
23
24 authorized_submit_users = "inline:{ gkleen= }";
25 authorized_flush_users = "inline:{ gkleen= }";
26 authorized_mailq_users = "inline:{ gkleen= }";
27
28 smtp_generic_maps = "inline:{ root=root+sif }";
29
30 mynetworks = ["127.0.0.0/8" "[::1]/128"];
31 smtpd_client_restrictions = ["permit_mynetworks" "reject"];
32 smtpd_relay_restrictions = ["permit_mynetworks" "reject"];
33
34 sender_dependent_default_transport_maps = ''regexp:${pkgs.writeText "sender_relay" ''
35 /@(cip|stud)\.ifi\.(lmu|uni-muenchen)\.de$/ smtp:smtp.ifi.lmu.de
36 /@ifi\.(lmu|uni-muenchen)\.de$/ smtp:smtpin1.ifi.lmu.de:587
37 /@math(ematik)?\.(lmu|uni-muenchen)\.de$/ smtps:smtp.math.lmu.de:465
38 /@(campus\.)?lmu\.de$/ smtp:postout.lrz.de
39 ''}'';
40 sender_bcc_maps = ''regexp:${pkgs.writeText "sender_bcc" ''
41 /^uni2work(-[^@]*)?@ifi\.lmu\.de$/ uni2work@ifi.lmu.de
42 /@ifi\.lmu\.de$/ gregor.kleen@ifi.lmu.de
43 ''}'';
44 relayhost = "[surtr.yggdrasil.li]:465";
45 default_transport = "relay";
46
47 smtp_sasl_auth_enable = true;
48 smtp_sender_dependent_authentication = true;
49 smtp_sasl_tls_security_options = "noanonymous";
50 smtp_sasl_mechanism_filter = ["plain"];
51 smtp_sasl_password_maps = "regexp:/run/credentials/postfix.service/sasl_passwd";
52 smtp_cname_overrides_servername = false;
53 smtp_always_send_ehlo = true;
54 smtp_tls_security_level = "dane";
55
56 smtp_tls_loglevel = "1";
57 smtp_dns_support_level = "dnssec";
58 };
59 masterConfig = {
60 submission = {
61 type = "inet";
62 private = false;
63 command = "smtpd";
64 args = [
65 "-o" "syslog_name=postfix/$service_name"
66 ];
67 };
68 smtp = { };
69 smtps = {
70 type = "unix";
71 private = true;
72 privileged = true;
73 chroot = false;
74 command = "smtp";
75 args = [
76 "-o" "smtp_tls_wrappermode=yes"
77 "-o" "smtp_tls_security_level=encrypt"
78 ];
79 };
80 relay = {
81 command = "smtp";
82 args = [
83 "-o" "smtp_fallback_relay="
84 "-o" "smtp_tls_security_level=verify"
85 "-o" "smtp_tls_wrappermode=yes"
86 "-o" "smtp_tls_cert_file=${./relay.crt}"
87 "-o" "smtp_tls_key_file=/run/credentials/postfix.service/relay.key"
88 ];
89 };
90 };
91 };
92
93 systemd.services.postfix = {
94 serviceConfig.LoadCredential = [
95 "sasl_passwd:${config.sops.secrets."postfix-sasl-passwd".path}"
96 "relay.key:${config.sops.secrets."relay-key".path}"
97 ];
98 };
99
100 sops.secrets = {
101 postfix-sasl-passwd = {
102 key = "sasl-passwd";
103 sopsFile = ./secrets.yaml;
104 };
105 relay-key = {
106 format = "binary";
107 sopsFile = ./relay.key;
108 };
109 };
110}
diff --git a/hosts/sif/email/relay.crt b/hosts/sif/email/relay.crt
new file mode 100644
index 00000000..ac13e7cb
--- /dev/null
+++ b/hosts/sif/email/relay.crt
@@ -0,0 +1,11 @@
1-----BEGIN CERTIFICATE-----
2MIIBjDCCAQygAwIBAgIPQAAAAGgLfNoL/PSMAsutMAUGAytlcTAXMRUwEwYDVQQD
3DAx5Z2dkcmFzaWwubGkwHhcNMjUwNDI1MTIwOTQ1WhcNMzUwNDI2MTIxNDQ1WjAR
4MQ8wDQYDVQQDDAZna2xlZW4wKjAFBgMrZXADIQB3outi3/3F4YO7Q97WAAaMHW0a
5m+Blldrgee+EZnWnD6N1MHMwHwYDVR0jBBgwFoAUTtn+VjMw6Ge1f68KD8dT1CWn
6l3YwHQYDVR0OBBYEFFOa4rYZYMbXUVdKv98NB504GUhjMA4GA1UdDwEB/wQEAwID
76DAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMCMAUGAytlcQNzABC0
80UgIt7gLZrU1TmzGoqPBris8R1DbKOJacicF5CU0MIIjHcX7mPFW8KtB4qm6KcPq
9kF6IaEPmgKpX3Nubk8HJik9vhIy9ysfINcVTvzXx8pO1bxbvREJRyA/apj10nzav
10yauId0cXHvN6g5RLAMsMAA==
11-----END CERTIFICATE-----
diff --git a/hosts/sif/email/relay.key b/hosts/sif/email/relay.key
new file mode 100644
index 00000000..412a44e0
--- /dev/null
+++ b/hosts/sif/email/relay.key
@@ -0,0 +1,19 @@
1{
2 "data": "ENC[AES256_GCM,data:lBlTuzOS75pvRmcTKT4KhHMH44RlE2SvCFAUP+GfsXws1Uai7DZ1MmbhvxxCa+pcLW19+sQYxrXLRNZWby1yOeKBJ2UQeYV5LOk9LSL/WIE3FZkCo5Dv0O0gSFKjjb61WN22a4JnHbLWADf/mLT3GZv91XfvFDo=,iv:ho8wQH3UNzX9JPW5gVcUGtxZzdVwsMFus0Z4KYe5t48=,tag:dAgZyHOva2xVVhE1nTl+lg==,type:str]",
3 "sops": {
4 "age": [
5 {
6 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
7 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6eTVRSUdFNUZGZmcxSUlT\nWmlsOGNyWXIzMGNTZjlKbXlhcEdZUXFRVkR3Cll0T0RMd0h2UW16QkR3SHlhYmNZ\nNDFrYXh3Rkp5NWsvcWc3UFJJaHVwT1UKLS0tIHhXVEI0VHBZVkpDQ1FzWENjMmJH\nb1FQWXVUUTBiZ1pKWG00MTNqVEo2SjAKK3VOU+QgRuxWYWEcrJiVMRFCprBICz4F\ngD+9zuPUzPezyJkYwTs+M+wX5GYkXppqm5W58yQLS2UDD38sr+SRjg==\n-----END AGE ENCRYPTED FILE-----\n"
8 },
9 {
10 "recipient": "age1fj65apkhfkrwyv5tx6zcs9nkjg8267fy733qph30sc7zfn7vapjqkd5kne",
11 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzWmJmZDVFazN2bDY1TkNG\nNXpJN2twMFFjZUxMTVdSNzJwQTFiYktrcGdrCjk4eFVHTko0bFVMSlFFWm9tbjMr\nbWNHMEQ1Rm1qUVhodlB1RGw2aDc4TUEKLS0tIERBK0J5NkN4OXJEZ1ZOZXhNc1Jm\naWNnUmZGbTIxdmNkYi9TZ2h2bGs3MVEKPQGaEf7M/5/xvSOfawpIp50fB3QfFSuz\nPgkrPMneaBeUx+uBYMyEFX4rpzLIBR3pnYMjAfoc+bjWaOtGQuEqyQ==\n-----END AGE ENCRYPTED FILE-----\n"
12 }
13 ],
14 "lastmodified": "2025-04-25T12:14:44Z",
15 "mac": "ENC[AES256_GCM,data:pObl2bJA93az9E3Ya+hA3ekI8TKKZ9NNTi0KzmWZBOiQwi9FuQYtpnmmT80L1KXWyOKJV6wGdAri3mNe/ue2S0TziSbQ/4+Dj4ubFKgkH7thb5q2dFyxw5FzhYzRQiXFqD/pxcNN9uL0lQI2Al0Eci0zX8Kcd1rAQ6RzLEoSmco=,iv:zo/3QFKTUEDxLy1k5yyU7Z1JMZ7cKdYUc6GHjaTTZKQ=,tag:f63Eja3lBfwJCYAOyEt56g==,type:str]",
16 "unencrypted_suffix": "_unencrypted",
17 "version": "3.10.2"
18 }
19}
diff --git a/hosts/sif/mail/secrets.yaml b/hosts/sif/email/secrets.yaml
index 3c74b710..3c74b710 100644
--- a/hosts/sif/mail/secrets.yaml
+++ b/hosts/sif/email/secrets.yaml
diff --git a/hosts/sif/greetd/default.nix b/hosts/sif/greetd/default.nix
index f609fc05..37ca13c5 100644
--- a/hosts/sif/greetd/default.nix
+++ b/hosts/sif/greetd/default.nix
@@ -11,6 +11,11 @@
11 # exec ${pkgs.dbus}/bin/dbus-run-session ${lib.getExe pkgs.cage} ${lib.escapeShellArgs cfg.cageArgs} -- ${lib.getExe cfg.package} 11 # exec ${pkgs.dbus}/bin/dbus-run-session ${lib.getExe pkgs.cage} ${lib.escapeShellArgs cfg.cageArgs} -- ${lib.getExe cfg.package}
12 # ''; 12 # '';
13 }; 13 };
14 systemd.services.greetd.environment = {
15 XKB_DEFAULT_LAYOUT = "us,us";
16 XKB_DEFAULT_VARIANT = "dvp,";
17 XKB_DEFAULT_OPTIONS = "compose:caps,grp:win_space_toggle";
18 };
14 programs.regreet = { 19 programs.regreet = {
15 enable = true; 20 enable = true;
16 theme = { 21 theme = {
diff --git a/hosts/sif/mail/default.nix b/hosts/sif/mail/default.nix
deleted file mode 100644
index 8d6cd705..00000000
--- a/hosts/sif/mail/default.nix
+++ /dev/null
@@ -1,70 +0,0 @@
1{ config, lib, pkgs, ... }:
2{
3 services.postfix = {
4 enable = true;
5 enableSmtp = true;
6 enableSubmission = false;
7 setSendmail = true;
8 networksStyle = "host";
9 hostname = "sif.midgard.yggdrasil";
10 destination = [];
11 relayHost = "uucp:ymir";
12 recipientDelimiter = "+";
13 masterConfig = {
14 uucp = {
15 type = "unix";
16 private = true;
17 privileged = true;
18 chroot = false;
19 command = "pipe";
20 args = [ "flags=Fqhu" "user=uucp" ''argv=${config.security.wrapperDir}/uux -z -a $sender - $nexthop!rmail ($recipient)'' ];
21 };
22 smtps = {
23 type = "unix";
24 private = true;
25 privileged = true;
26 chroot = false;
27 command = "smtp";
28 args = [ "-o" "smtp_tls_wrappermode=yes" "-o" "smtp_tls_security_level=encrypt" ];
29 };
30 };
31 config = {
32 default_transport = "uucp:ymir";
33
34 inet_interfaces = "loopback-only";
35
36 authorized_submit_users = ["!uucp" "static:anyone"];
37 message_size_limit = "0";
38
39 sender_dependent_default_transport_maps = ''regexp:${pkgs.writeText "sender_relay" ''
40 /@(cip|stud)\.ifi\.(lmu|uni-muenchen)\.de$/ smtp:smtp.ifi.lmu.de
41 /@ifi\.(lmu|uni-muenchen)\.de$/ smtp:smtpin1.ifi.lmu.de:587
42 /@math(ematik)?\.(lmu|uni-muenchen)\.de$/ smtps:smtp.math.lmu.de:465
43 /@(campus\.)?lmu\.de$/ smtp:postout.lrz.de
44 ''}'';
45 sender_bcc_maps = ''regexp:${pkgs.writeText "sender_bcc" ''
46 /^uni2work(-[^@]*)?@ifi\.lmu\.de$/ uni2work@ifi.lmu.de
47 /@ifi\.lmu\.de$/ gregor.kleen@ifi.lmu.de
48 ''}'';
49
50 smtp_sasl_auth_enable = true;
51 smtp_sender_dependent_authentication = true;
52 smtp_sasl_tls_security_options = "noanonymous";
53 smtp_sasl_mechanism_filter = ["plain"];
54 smtp_sasl_password_maps = "regexp:/var/db/postfix/sasl_passwd";
55 smtp_cname_overrides_servername = false;
56 smtp_always_send_ehlo = true;
57 smtp_tls_security_level = "dane";
58
59 smtp_tls_loglevel = "1";
60 smtp_dns_support_level = "dnssec";
61 };
62 };
63
64 sops.secrets.postfix-sasl-passwd = {
65 key = "sasl-passwd";
66 path = "/var/db/postfix/sasl_passwd";
67 owner = "postfix";
68 sopsFile = ./secrets.yaml;
69 };
70}
diff --git a/hosts/sif/ruleset.nft b/hosts/sif/ruleset.nft
index 2af8b2ee..62339f69 100644
--- a/hosts/sif/ruleset.nft
+++ b/hosts/sif/ruleset.nft
@@ -61,7 +61,7 @@ table inet filter {
61 counter mosh-rx {} 61 counter mosh-rx {}
62 counter wg-rx {} 62 counter wg-rx {}
63 counter yggdrasil-gre-rx {} 63 counter yggdrasil-gre-rx {}
64 counter quickserve-rx {} 64 counter miniserve-rx {}
65 counter ausweisapp2-rx {} 65 counter ausweisapp2-rx {}
66 66
67 counter established-rx {} 67 counter established-rx {}
@@ -81,7 +81,7 @@ table inet filter {
81 counter mosh-tx {} 81 counter mosh-tx {}
82 counter wg-tx {} 82 counter wg-tx {}
83 counter yggdrasil-gre-tx {} 83 counter yggdrasil-gre-tx {}
84 counter quickserve-tx {} 84 counter miniserve-tx {}
85 85
86 counter tx {} 86 counter tx {}
87 87
@@ -134,7 +134,7 @@ table inet filter {
134 tcp dport 22 counter name ssh-rx accept 134 tcp dport 22 counter name ssh-rx accept
135 udp dport 60000-61000 counter name mosh-rx accept 135 udp dport 60000-61000 counter name mosh-rx accept
136 136
137 tcp dport 8000 counter name quickserve-rx accept 137 tcp dport 8080 counter name miniserve-rx accept
138 udp dport 24727 counter name ausweisapp2-rx accept 138 udp dport 24727 counter name ausweisapp2-rx accept
139 139
140 udp dport 51820-51822 counter name wg-rx accept 140 udp dport 51820-51822 counter name wg-rx accept
@@ -173,7 +173,7 @@ table inet filter {
173 udp sport 51820-51822 counter name wg-tx 173 udp sport 51820-51822 counter name wg-tx
174 iifname "yggdrasil-wg-*" meta l4proto gre counter name yggdrasil-gre-tx 174 iifname "yggdrasil-wg-*" meta l4proto gre counter name yggdrasil-gre-tx
175 175
176 tcp sport 8000 counter name quickserve-tx accept 176 tcp sport 8080 counter name miniserve-tx accept
177 177
178 oifname virbr0 udp sport 67 counter name libvirt-dhcp accept 178 oifname virbr0 udp sport 67 counter name libvirt-dhcp accept
179 oifname virbr0 udp sport 547 counter name libvirt-dhcp accept 179 oifname virbr0 udp sport 547 counter name libvirt-dhcp accept
diff --git a/hosts/surtr/audiobookshelf.nix b/hosts/surtr/audiobookshelf.nix
new file mode 100644
index 00000000..728851a4
--- /dev/null
+++ b/hosts/surtr/audiobookshelf.nix
@@ -0,0 +1,66 @@
1{ config, ... }:
2
3{
4 config = {
5 security.acme.rfc2136Domains = {
6 "audiobookshelf.yggdrasil.li" = {
7 restartUnits = ["nginx.service"];
8 };
9 };
10
11 services.nginx = {
12 upstreams."audiobookshelf" = {
13 servers = {
14 "[2a03:4000:52:ada:4:1::]:28982" = {};
15 };
16 extraConfig = ''
17 keepalive 8;
18 '';
19 };
20 virtualHosts = {
21 "audiobookshelf.yggdrasil.li" = {
22 kTLS = true;
23 http3 = true;
24 forceSSL = true;
25 sslCertificate = "/run/credentials/nginx.service/audiobookshelf.yggdrasil.li.pem";
26 sslCertificateKey = "/run/credentials/nginx.service/audiobookshelf.yggdrasil.li.key.pem";
27 sslTrustedCertificate = "/run/credentials/nginx.service/audiobookshelf.yggdrasil.li.chain.pem";
28 extraConfig = ''
29 charset utf-8;
30 '';
31
32 locations = {
33 "/".extraConfig = ''
34 proxy_pass http://audiobookshelf;
35
36 proxy_http_version 1.1;
37 proxy_set_header Upgrade $http_upgrade;
38 proxy_set_header Connection "upgrade";
39
40 proxy_redirect off;
41 proxy_set_header Host $host;
42 proxy_set_header X-Real-IP $remote_addr;
43 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
44 proxy_set_header X-Forwarded-Host $server_name;
45 proxy_set_header X-Forwarded-Proto $scheme;
46
47 client_max_body_size 0;
48 proxy_request_buffering off;
49 proxy_buffering off;
50 '';
51 };
52 };
53 };
54 };
55
56 systemd.services.nginx = {
57 serviceConfig = {
58 LoadCredential = [
59 "audiobookshelf.yggdrasil.li.key.pem:${config.security.acme.certs."audiobookshelf.yggdrasil.li".directory}/key.pem"
60 "audiobookshelf.yggdrasil.li.pem:${config.security.acme.certs."audiobookshelf.yggdrasil.li".directory}/fullchain.pem"
61 "audiobookshelf.yggdrasil.li.chain.pem:${config.security.acme.certs."audiobookshelf.yggdrasil.li".directory}/chain.pem"
62 ];
63 };
64 };
65 };
66}
diff --git a/hosts/surtr/bifrost/default.nix b/hosts/surtr/bifrost/default.nix
index fbfde757..52ab43f5 100644
--- a/hosts/surtr/bifrost/default.nix
+++ b/hosts/surtr/bifrost/default.nix
@@ -18,7 +18,7 @@ in {
18 ListenPort = 51822; 18 ListenPort = 51822;
19 }; 19 };
20 wireguardPeers = [ 20 wireguardPeers = [
21 { AllowedIPs = [ "2a03:4000:52:ada:4:1::/96" ]; 21 { AllowedIPs = [ "2a03:4000:52:ada:4:1::/96" "2a03:4000:52:ada:6::/80" ];
22 PublicKey = trim (readFile ../../vidhar/network/bifrost/vidhar.pub); 22 PublicKey = trim (readFile ../../vidhar/network/bifrost/vidhar.pub);
23 } 23 }
24 ]; 24 ];
@@ -34,6 +34,8 @@ in {
34 routes = [ 34 routes = [
35 { Destination = "2a03:4000:52:ada:4::/80"; 35 { Destination = "2a03:4000:52:ada:4::/80";
36 } 36 }
37 { Destination = "2a03:4000:52:ada:6::/80";
38 }
37 ]; 39 ];
38 linkConfig = { 40 linkConfig = {
39 RequiredForOnline = false; 41 RequiredForOnline = false;
diff --git a/hosts/surtr/default.nix b/hosts/surtr/default.nix
index b8a639d5..63beece3 100644
--- a/hosts/surtr/default.nix
+++ b/hosts/surtr/default.nix
@@ -7,6 +7,7 @@ with lib;
7 tmpfs-root qemu-guest openssh rebuild-machines zfs 7 tmpfs-root qemu-guest openssh rebuild-machines zfs
8 ./zfs.nix ./dns ./tls ./http ./bifrost ./matrix ./postgresql 8 ./zfs.nix ./dns ./tls ./http ./bifrost ./matrix ./postgresql
9 ./prometheus ./email ./vpn ./borg.nix ./etebase ./immich.nix 9 ./prometheus ./email ./vpn ./borg.nix ./etebase ./immich.nix
10 ./paperless.nix ./hledger.nix ./audiobookshelf.nix ./kimai.nix
10 ]; 11 ];
11 12
12 config = { 13 config = {
@@ -14,9 +15,6 @@ with lib;
14 system = "x86_64-linux"; 15 system = "x86_64-linux";
15 }; 16 };
16 17
17 networking.hostId = "a64cf4d7";
18 environment.etc."machine-id".text = "a64cf4d793ab0a0ed3892ead609fc0bc";
19
20 boot = { 18 boot = {
21 loader.grub = { 19 loader.grub = {
22 enable = true; 20 enable = true;
@@ -24,12 +22,20 @@ with lib;
24 device = "/dev/vda"; 22 device = "/dev/vda";
25 }; 23 };
26 24
27
28 tmp.useTmpfs = true; 25 tmp.useTmpfs = true;
29 26
30 zfs.devNodes = "/dev"; # /dev/vda2 does not show up in /dev/disk/by-id 27 zfs.devNodes = "/dev"; # /dev/vda2 does not show up in /dev/disk/by-id
31 28
32 kernelModules = ["ptp_kvm"]; 29 kernelModules = ["ptp_kvm"];
30 kernelPatches = [
31 { name = "zswap-default";
32 patch = null;
33 extraStructuredConfig = with lib.kernel; {
34 ZSWAP_DEFAULT_ON = yes;
35 ZSWAP_SHRINKER_DEFAULT_ON = yes;
36 };
37 }
38 ];
33 }; 39 };
34 40
35 fileSystems = { 41 fileSystems = {
@@ -38,6 +44,9 @@ with lib;
38 fsType = "vfat"; 44 fsType = "vfat";
39 }; 45 };
40 }; 46 };
47 swapDevices = [
48 { label = "swap"; }
49 ];
41 50
42 networking = { 51 networking = {
43 hostName = "surtr"; 52 hostName = "surtr";
@@ -163,11 +172,6 @@ with lib;
163 stateful = true; 172 stateful = true;
164 }; 173 };
165 174
166 zramSwap = {
167 enable = true;
168 algorithm = "zstd";
169 };
170
171 systemd.sysusers.enable = false; 175 systemd.sysusers.enable = false;
172 system.etc.overlay.mutable = true; 176 system.etc.overlay.mutable = true;
173 boot.enableContainers = true; 177 boot.enableContainers = true;
diff --git a/hosts/surtr/dns/Gupfile b/hosts/surtr/dns/Gupfile
index ac96f620..70674cce 100644
--- a/hosts/surtr/dns/Gupfile
+++ b/hosts/surtr/dns/Gupfile
@@ -1,2 +1,2 @@
1key.gup: 1key.gup:
2 keys/*.yaml \ No newline at end of file 2 keys/* \ No newline at end of file
diff --git a/hosts/surtr/dns/default.nix b/hosts/surtr/dns/default.nix
index ee1d089d..8aca2b97 100644
--- a/hosts/surtr/dns/default.nix
+++ b/hosts/surtr/dns/default.nix
@@ -157,7 +157,7 @@ in {
157 ${concatMapStringsSep "\n" mkZone [ 157 ${concatMapStringsSep "\n" mkZone [
158 { domain = "yggdrasil.li"; 158 { domain = "yggdrasil.li";
159 addACLs = { "yggdrasil.li" = ["ymir_acme_acl"]; }; 159 addACLs = { "yggdrasil.li" = ["ymir_acme_acl"]; };
160 acmeDomains = ["surtr.yggdrasil.li" "yggdrasil.li" "etesync.yggdrasil.li" "immich.yggdrasil.li" "app.etesync.yggdrasil.li"]; 160 acmeDomains = ["surtr.yggdrasil.li" "yggdrasil.li" "etesync.yggdrasil.li" "immich.yggdrasil.li" "app.etesync.yggdrasil.li" "paperless.yggdrasil.li" "hledger.yggdrasil.li" "audiobookshelf.yggdrasil.li" "kimai.yggdrasil.li"];
161 } 161 }
162 { domain = "nights.email"; 162 { domain = "nights.email";
163 addACLs = { "nights.email" = ["ymir_acme_acl"]; }; 163 addACLs = { "nights.email" = ["ymir_acme_acl"]; };
@@ -169,15 +169,9 @@ in {
169 { domain = "kleen.li"; 169 { domain = "kleen.li";
170 addACLs = { "kleen.li" = ["ymir_acme_acl"]; }; 170 addACLs = { "kleen.li" = ["ymir_acme_acl"]; };
171 } 171 }
172 { domain = "xmpp.li";
173 addACLs = { "xmpp.li" = ["ymir_acme_acl"]; };
174 }
175 { domain = "synapse.li"; 172 { domain = "synapse.li";
176 acmeDomains = ["element.synapse.li" "turn.synapse.li" "synapse.li"]; 173 acmeDomains = ["element.synapse.li" "turn.synapse.li" "synapse.li"];
177 } 174 }
178 { domain = "dirty-haskell.org";
179 addACLs = { "dirty-haskell.org" = ["ymir_acme_acl"]; };
180 }
181 { domain = "praseodym.org"; 175 { domain = "praseodym.org";
182 addACLs = { "praseodym.org" = ["ymir_acme_acl"]; }; 176 addACLs = { "praseodym.org" = ["ymir_acme_acl"]; };
183 } 177 }
diff --git a/hosts/surtr/dns/key.gup b/hosts/surtr/dns/key.gup
index 32d4f7d6..5b5058b3 100644
--- a/hosts/surtr/dns/key.gup
+++ b/hosts/surtr/dns/key.gup
@@ -3,4 +3,4 @@
3keyName=${${2:t}%.yaml}_key 3keyName=${${2:t}%.yaml}_key
4 4
5keymgr -t ${keyName} > $1 5keymgr -t ${keyName} > $1
6sops -p '7ED22F4AA7BB55728B643DC5471B7D88E4EF66F8,30D3453B8CD02FE2A3E7C78C0FB536FB87AE8F51' --input-type=binary --output-type=binary -e -i $1 \ No newline at end of file 6sops --input-type=binary --output-type=binary -e -i $1
diff --git a/hosts/surtr/dns/keys/audiobookshelf.yggdrasil.li_acme b/hosts/surtr/dns/keys/audiobookshelf.yggdrasil.li_acme
new file mode 100644
index 00000000..e3af9966
--- /dev/null
+++ b/hosts/surtr/dns/keys/audiobookshelf.yggdrasil.li_acme
@@ -0,0 +1,19 @@
1{
2 "data": "ENC[AES256_GCM,data:5BLk/MZtQsLjpdKGiZjtOH1soIXB05MkEoPWyr5m27ho7H5udDjRZE0/OjvSlMzQNjFNJc+OwbeHwaJ8sPLCnXqoFHJXikAmi0gpdFC0uN0JGYCBT1EaK7j6yVHyiqFXo6xwVSCO/zP/fbjItiuqU+B4llGx5N2I4HQqRLFoW/35PZazkR15xI1zo8LeC8T+jm16apFQw2Ih/RqsJK7XlHqnXq9SzeA2qhgkCbJf6aJ6zDS7eQVFt7qHh2mVtV7Al++bZiIkJiNJ5SJO1ck18w2t9HwXBhfMFKXvfFW0I6kKur1qSJk=,iv:rxm5PwOzXDaK+nj2k3bUqlYbIFFA49ispyfamtQqU/A=,tag:7dOua39f+0JsLldLRLr1NQ==,type:str]",
3 "sops": {
4 "age": [
5 {
6 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
7 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwV1hIcWNNRVJXOG1xYlFK\nb1VxWG03dGVmS3dmQTltUTR0VmEzYjRFanlBCnE1M1BTZjNCQnpsNkU5ZlR5T3BR\nOHliWUV2bXMrK0w1K3JXbUE0dEhKdzgKLS0tIDdkUEZ4QUE0NUN2TXFYcklybjBS\nRVpxV2J6U1BnUng1dytWTGRkd0FrRGcKVaMCzcrX+BQqbmh95JEk3lRdfnv9uO8n\nwotKxp/+xX7GEF42BOzJ/mWcgyVjABlnWeVyaRxfpbCUrmNuFYO6XA==\n-----END AGE ENCRYPTED FILE-----\n"
8 },
9 {
10 "recipient": "age19a7j77w267z04zls7m28a8hj4a0g5af6ltye2d5wypg33c3l89csd4r9zq",
11 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkZmRFR0gwc1pjVVorK0t6\nVHU4U0xob09Dd2J5QUJXbndZdWp3cytpYWhRCnFnMlZ0cjNGdXN1RWEvQlVHSE1M\nMEdzSjlEb2NkaE1zMEJJOTlCMC9FVFEKLS0tIGJiTEJSRVZZbVUzZVFGeXQwYm1w\ndWVYQVdoVlJnRENTYzhnQm1BcWVRdW8K0ECfLVQBQuZWFFFjdHLcJBz+CDzsOOwh\nOA38qxHD4EWfXR1c3G8RDbow7nB2MGc1Zohc7qhtuTj2wL0qhVKWqA==\n-----END AGE ENCRYPTED FILE-----\n"
12 }
13 ],
14 "lastmodified": "2025-05-09T17:07:16Z",
15 "mac": "ENC[AES256_GCM,data:K5I6YXQXCUPHFBNVlXIdLLKqiNPVZh95KoHni2m16SdAvTyBab79SZ5xNvotrtKXp0iISCogEgdMm+OWbxYywEiZ+sUsxgx6RE5nAXruZOiAwzuyUr88qgCHTBZnKzgaDZlbYOgWB+LCzr8s5JbcTD4G6/RaXYyqmx7igygubHA=,iv:Zbij4eoDqoP5XYhAsDGBGqlcP5ACQAY/QngTmrJYRzs=,tag:WYxhUEZwmXkQmnpyOuP2Bg==,type:str]",
16 "unencrypted_suffix": "_unencrypted",
17 "version": "3.10.2"
18 }
19}
diff --git a/hosts/surtr/dns/keys/hledger.yggdrasil.li_acme b/hosts/surtr/dns/keys/hledger.yggdrasil.li_acme
new file mode 100644
index 00000000..b3f4cfb6
--- /dev/null
+++ b/hosts/surtr/dns/keys/hledger.yggdrasil.li_acme
@@ -0,0 +1,24 @@
1{
2 "data": "ENC[AES256_GCM,data:lCj8VYJL9z29FJ154XQtxKQLwwitCRGy4krJ6u8yw2FMzoHprEpFgm33+mFspxSKk/It2G8cfTGMZSeVkYJEHb66HNKHl0A2Fz3hwjpRjh1MZAw0wiZJlnS/LNqoGstQ2PJmTQTW3aJRMoT1GS7q/gSp/3rqySA5EOm0GgUiA3Vi7nGpkBenKDEbQbcIBXRdMOk66BCdiz5XGm/1VLQQLO9oVwY2KBnLaZSISohyGVhbIy7GT2ygoWHHxHn0c5CRVNvGNwesM1gO1NnTFrISLMWSrsDPaAtQ,iv:fa8LFjzqsf2ccfbEe5MOmerb7FzXb4xr24y1GWIMT1Q=,tag:7oQ54DKBb76Pbw1lmEHt+Q==,type:str]",
3 "sops": {
4 "kms": null,
5 "gcp_kms": null,
6 "azure_kv": null,
7 "hc_vault": null,
8 "age": [
9 {
10 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
11 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzcHJLRHh0VkpDNDU3cGZS\nMWFlVWpFemZ2RnpnSllDWU1EWGoyWUxyejNzClBGandzaFI5NXY1bG51Y3VxRk9r\nc29NbXBOaEZDblBuaVowemQydkxBdjgKLS0tICtVb2xkMmh4T0Q0cU4xQnBzZExI\namFRUnRYTWIyQ3RHNUVHWTFrUzhhK1UKqmATNmxlhkxM5PP1U6w7fSYVA8AgIRAt\nJ9WZrTffQfXMdw4RmjWcoVHFH39Fe4SteedxliCCcqjkjgSEB4Rgow==\n-----END AGE ENCRYPTED FILE-----\n"
12 },
13 {
14 "recipient": "age19a7j77w267z04zls7m28a8hj4a0g5af6ltye2d5wypg33c3l89csd4r9zq",
15 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjWlhpQWI4c0x0ZXRGYVE2\nR1dHZzZud0ZxQWsrREhJWUowWE1zem5FVFI0CkJBUnIwY1FGS3N2VnpuSkZjRllZ\ncVgyeVg4cTVjRitzL0RKb0ZQb3BsOEkKLS0tIGs3SDBkamVBNDhQUlh5dmVVZXJs\nM3VKdlFKc21GcFY0UUtiaHFvYWI4V0kKKuWYEncxe9NT2ZS3X3+l/gT4BQOrdCg8\nj2jGL+Yzy/356GO3PFTn2HHLam6KWDKaYB5TlK/zSohfUt5giQH2Lw==\n-----END AGE ENCRYPTED FILE-----\n"
16 }
17 ],
18 "lastmodified": "2025-02-19T17:13:51Z",
19 "mac": "ENC[AES256_GCM,data:XsBdMCBjB+YuBMZQrjJ5uZtaYKSqsdWVvm+IEoJflCKPIhPk2rBZ3nY8KngXFbq2fWgsYyTM83kb2trEGIEHUPuERt+mgfCI3bSlylriwgsDWihCjyBecNE+BbdXE0+YcNl8pIwBU4M+3f2StQMH22YamToLJ9i9kfKcBrirDuU=,iv:VTIdBVY3kVBMYWhYUmrP2vZ9rpH90DzF68y1aDf2EAs=,tag:YkL+nw6LNXAceZtx9vgf6A==,type:str]",
20 "pgp": null,
21 "unencrypted_suffix": "_unencrypted",
22 "version": "3.9.4"
23 }
24} \ No newline at end of file
diff --git a/hosts/surtr/dns/keys/kimai.yggdrasil.li_acme b/hosts/surtr/dns/keys/kimai.yggdrasil.li_acme
new file mode 100644
index 00000000..bdfb135a
--- /dev/null
+++ b/hosts/surtr/dns/keys/kimai.yggdrasil.li_acme
@@ -0,0 +1,19 @@
1{
2 "data": "ENC[AES256_GCM,data:sKFt4pH0Xn7Qm6JFMg/2N7Ht7jtMJukfN+U3dQaoYXPbhRJ+heEtDpXV/WP4AlfbfpIOgTPW3mcmQCwKFNhS00vEsQA4728FfXZzDDmZCa3hwg51wDbL7XUOr0OePgzi86lt0Q193K6CkGqEAa1vFIb//ElEfBYIwdATbmcoAsM3mHhz58X7c1qf8LNuB93o/1N2xXXZI3NWOhOjlviTc2DAhffXDwlMJSYUhldnwtDKmLM1mooJzLgm2p9w7gRD7WPqEqZFq9uFDK69P9uX5T9hFHg=,iv:rAE4sYxxLou4tyD4RWTp3LjQP0cya95coy1MvwfEK/U=,tag:u4SSk8SZFlj0ks7d6tDocw==,type:str]",
3 "sops": {
4 "age": [
5 {
6 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
7 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2KzdNUWhEcDB6QmtUTnVh\nNS9Nc2I4UjAzekxhRXo1UmY3SklPejV1TURJCm9NY2lVOERoMDFKTU56Mmh1NHEr\naGV4M1RoVldHV0xyc3Z0MnVqakpjMFUKLS0tIEYxSk9OUm9kMkdtcG5POWRGQVkx\nY1FEaXYwMGo0L0Z0aTVTZDA5aUFDWEUKJ+e/7lR/rNPNVnIy+wkiKiAYMxWp4L7q\nwnSTx451vSnxv9j3JWB43Y7XQC08cisWDj06ULw8FnEbKYOvTYj9mQ==\n-----END AGE ENCRYPTED FILE-----\n"
8 },
9 {
10 "recipient": "age19a7j77w267z04zls7m28a8hj4a0g5af6ltye2d5wypg33c3l89csd4r9zq",
11 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwOTU3dUEzaXM5T1VkbDRO\nMm14OG1mUkk2bDRhdnBsMHBkc3kvUzlyNlQwCktFSHJhMnhoQ2J6bC9vUHNLWTRC\nRFpYeHo3N2xjWUhjQnRwQ2Nrc1pRUmsKLS0tIDdPeFBVdkxDd1JWSmcxQ0tLMTBD\ncHU3VExZOUhYUlJvbGNoK3FMK2VIbGMKFk94P9aBY04CPIi983f3Aalgh4fnU+/K\n2mxawSMf9jz8704N5XJfmr2hwNy8hqLIn8bjsEMAPTfE1YBGga4w0g==\n-----END AGE ENCRYPTED FILE-----\n"
12 }
13 ],
14 "lastmodified": "2025-05-24T09:42:23Z",
15 "mac": "ENC[AES256_GCM,data:diCeJGvBmM0Ng722eKoFwDe7pqZrdLPSLn5j9LfdaFI64BAbSbA5bAq4NFXqdJ1vttarD2A5rEafYoXUxP8228x2GhNyWUGW5AWgBjVPUc59gjs4wYKR5HlkVMIadhTwNheEyoEjrxX40GNBgCG7X3ocOtOYKbKECp433gdAPDg=,iv:d+yJMWj2RyFnveo2ZNrpNeV+amXM+H7vdC0A2F7mwjA=,tag:yjibG2iusdprp0ORghYWhw==,type:str]",
16 "unencrypted_suffix": "_unencrypted",
17 "version": "3.10.2"
18 }
19}
diff --git a/hosts/surtr/dns/keys/paperless.yggdrasil.li_acme b/hosts/surtr/dns/keys/paperless.yggdrasil.li_acme
new file mode 100644
index 00000000..bc4640db
--- /dev/null
+++ b/hosts/surtr/dns/keys/paperless.yggdrasil.li_acme
@@ -0,0 +1,24 @@
1{
2 "data": "ENC[AES256_GCM,data:wOl8KLHD0H+btq0A3UreyVF9bOXZsiTwWJkVH8GubyIQDyiDC8vQm+dfv0rz8TwcBWYpC4aMIPPflG2HsdYO4rKGQ/nBmWmxhNXjpnyRo8iKM1BGb5bxNe4eVcUVhI60NuRJDRLmtDp+0rYGT/MVYp0/mHBINsQCXWBPDoaN2PI2GSnRag/x0wcL27xgH6NDd8glcdCN5nCAPDvazA3LialkXXv7/cceA5Q/Ee6HGzPP0w212/UvBm07Z5tXnHiy5cTbAGTUBfIqC8n501jtaQhpMh/yzA1R8KwUrw==,iv:bLzsthCaanNikNS2Es4J1++E5lijEbjyW5hU4zzNBcg=,tag:eWfZ3AtcSAGv8jWXzqlAwQ==,type:str]",
3 "sops": {
4 "kms": null,
5 "gcp_kms": null,
6 "azure_kv": null,
7 "hc_vault": null,
8 "age": [
9 {
10 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
11 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNYWZKOHd2UmxWYVlIamk4\nZ04zQnAwcDdsSDBDWUZnekNva3BzRERyWXljCmNvUU1Fczc2aUN6VGl6NEJ6MGIx\nWHVpeVluWnRnbjNadGxkSmYyNE1rZzQKLS0tIGpkYVZQRDJGS21ZZHdlRk1MMm02\nQ09aanNXSWltNi9QeUNtUVQ2UEZybmMK6/qcNYLMcyKTmtROX+ZsRqDxMXwkXiAV\ndsdsWJ5+zSJuK5SEIh0fqEZ/t4pxnMcr1WieETgLSd+w0sNQS7EKPQ==\n-----END AGE ENCRYPTED FILE-----\n"
12 },
13 {
14 "recipient": "age19a7j77w267z04zls7m28a8hj4a0g5af6ltye2d5wypg33c3l89csd4r9zq",
15 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSByRjdJOHdjamRHVjlZOTNV\nbTBuam5vNERIWTB6T0JkR2pLUnlQN1BGbUJNClhrZ2hPRWZtT3BERFNwdmNEMmVu\nT0dxcjNkNGIvMVJQWENoUmRhTGd6SXcKLS0tIDV6WDd1bks4K1VuVkgybjdMd0w0\nMHBsT3FmOWU0WnJsM2diQm1sTU1ON2MKtf5HZ0S1cLMx98vDKRKamS7aHIJZ0OnA\nzH4VoeVm+PKsOeqVfY+gMHLdaMEWLKYsz3B8bxIoL5pvnCdT1QAN2A==\n-----END AGE ENCRYPTED FILE-----\n"
16 }
17 ],
18 "lastmodified": "2025-02-13T19:23:42Z",
19 "mac": "ENC[AES256_GCM,data:o7zNTjkohzAouYpJUGqf8DUfYf4/g3GZgc+4cf+PjI0OF8uc1WDCPvliBFe6pf/8QMhV5DFWd2SfszWnpnQhtiIVG/2BEk5sw3P6r/SUbSErakFYHueVQKp+9rdxK6uKcHUYhO46E332AwIxTuvNeHtSBMxx0kAwQPuuD/u3L4A=,iv:aiM0sGyGMk5lfBOpB2bDFCY+UfWwyUNixieww6eOSLs=,tag:MI7xJ7RsyZgQfF1SBVVmcQ==,type:str]",
20 "pgp": null,
21 "unencrypted_suffix": "_unencrypted",
22 "version": "3.9.4"
23 }
24} \ No newline at end of file
diff --git a/hosts/surtr/dns/zones/email.nights.soa b/hosts/surtr/dns/zones/email.nights.soa
index 913a88d4..34209a99 100644
--- a/hosts/surtr/dns/zones/email.nights.soa
+++ b/hosts/surtr/dns/zones/email.nights.soa
@@ -1,7 +1,7 @@
1$ORIGIN nights.email. 1$ORIGIN nights.email.
2$TTL 3600 2$TTL 3600
3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li ( 3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li (
4 2023013000 ; serial 4 2025060700 ; serial
5 10800 ; refresh 5 10800 ; refresh
6 3600 ; retry 6 3600 ; retry
7 604800 ; expire 7 604800 ; expire
@@ -27,11 +27,7 @@ $TTL 3600
27 27
28_acme-challenge IN NS ns.yggdrasil.li. 28_acme-challenge IN NS ns.yggdrasil.li.
29 29
30ymir._domainkey IN TXT ( 30ymir._domainkey IN CNAME ymir._domainkey.yggdrasil.li.
31 "v=DKIM1;k=rsa;p=MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq3cCKlk+VPhyAanLZTM0BCzUT/+fmxHioZcFk0uJk1akBYj7BRofR7eVNcLKpm3rwYMQgE+9vJH9p8SV6tws9EcWc8SMCqqGZlREYM7PmLDiTSK/vjCzkygfgFCb0EBNsY2A/fpP4rTeoxrbcBSvMkq97iY5rwyw4wXZVZXLiDaCj23s8POoxTk1ClqUJZJQ5x2"
32 "qzrC0RfN5kLZ9A7Gq2jB09vNxpXHYqABA0bJv88JiZM7hfkp9IafJZ+yCVMaBcJs4DAxnTjNAuFD9gm+qSFVY8+yeXqL6Qjo5PbruhyZRBW8RgRYT8t5n07XRglMGKKGMwOGLanrltcyXqB+GsDZBD36RAAwjFadnxdpDyRv4SgRP7ff2tKRrORYpmpN+mKdqw5j3J/nP6bXV1oAkyh9XQkPEIDi81WT87EZziTElDzVp6A2qFOxqucAovoRk24"
33 "7vlsns1FApFRsp9mja0UZNObyKD1M6tP9Ep7lS76tFGMk+WDvXRJH5LEsyCpu7sSyl1r/O0M4K+KldRCqLlZd7rf8F5P8T0dn1azk05g7F4p0N/y9GNdzXbPZ9u0eZdI7SEdh8ZoOZp7NVZiBFfbWLSS5ZtyA2kbBa4i7GJ/cuAbEKOmqAkeQPiu96TGIcyjkXjS6mTPI+9UmKZYZC+OM8XdJ02y5KRoonCc19ZS8CAwEAAQ=="
34)
35 31
36_xmpp-client._tcp IN SRV 5 0 5222 ymir.yggdrasil.li. 32_xmpp-client._tcp IN SRV 5 0 5222 ymir.yggdrasil.li.
37_xmpp-server._tcp IN SRV 5 0 5269 ymir.yggdrasil.li. 33_xmpp-server._tcp IN SRV 5 0 5269 ymir.yggdrasil.li.
diff --git a/hosts/surtr/dns/zones/li.141.soa b/hosts/surtr/dns/zones/li.141.soa
index d42b4719..78d137bb 100644
--- a/hosts/surtr/dns/zones/li.141.soa
+++ b/hosts/surtr/dns/zones/li.141.soa
@@ -1,7 +1,7 @@
1$ORIGIN 141.li. 1$ORIGIN 141.li.
2$TTL 3600 2$TTL 3600
3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li ( 3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li (
4 2024102100 ; serial 4 2025060701 ; serial
5 10800 ; refresh 5 10800 ; refresh
6 3600 ; retry 6 3600 ; retry
7 604800 ; expire 7 604800 ; expire
@@ -45,11 +45,8 @@ ymir IN AAAA 2a03:4000:6:d004::
45ymir IN MX 0 ymir.yggdrasil.li 45ymir IN MX 0 ymir.yggdrasil.li
46ymir IN TXT "v=spf1 redirect=ymir.yggdrasil.li" 46ymir IN TXT "v=spf1 redirect=ymir.yggdrasil.li"
47 47
48ymir._domainkey IN TXT ( 48ymir._domainkey IN CNAME ymir._domainkey.yggdrasil.li.
49 "v=DKIM1;k=rsa;p=MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq3cCKlk+VPhyAanLZTM0BCzUT/+fmxHioZcFk0uJk1akBYj7BRofR7eVNcLKpm3rwYMQgE+9vJH9p8SV6tws9EcWc8SMCqqGZlREYM7PmLDiTSK/vjCzkygfgFCb0EBNsY2A/fpP4rTeoxrbcBSvMkq97iY5rwyw4wXZVZXLiDaCj23s8POoxTk1ClqUJZJQ5x2" 49surtr._domainkey IN CNAME surtr._domainkey.yggdrasil.li.
50 "qzrC0RfN5kLZ9A7Gq2jB09vNxpXHYqABA0bJv88JiZM7hfkp9IafJZ+yCVMaBcJs4DAxnTjNAuFD9gm+qSFVY8+yeXqL6Qjo5PbruhyZRBW8RgRYT8t5n07XRglMGKKGMwOGLanrltcyXqB+GsDZBD36RAAwjFadnxdpDyRv4SgRP7ff2tKRrORYpmpN+mKdqw5j3J/nP6bXV1oAkyh9XQkPEIDi81WT87EZziTElDzVp6A2qFOxqucAovoRk24"
51 "7vlsns1FApFRsp9mja0UZNObyKD1M6tP9Ep7lS76tFGMk+WDvXRJH5LEsyCpu7sSyl1r/O0M4K+KldRCqLlZd7rf8F5P8T0dn1azk05g7F4p0N/y9GNdzXbPZ9u0eZdI7SEdh8ZoOZp7NVZiBFfbWLSS5ZtyA2kbBa4i7GJ/cuAbEKOmqAkeQPiu96TGIcyjkXjS6mTPI+9UmKZYZC+OM8XdJ02y5KRoonCc19ZS8CAwEAAQ=="
52)
53 50
54_xmpp-client._tcp IN SRV 5 0 5222 ymir.yggdrasil.li. 51_xmpp-client._tcp IN SRV 5 0 5222 ymir.yggdrasil.li.
55_xmpp-server._tcp IN SRV 5 0 5269 ymir.yggdrasil.li. 52_xmpp-server._tcp IN SRV 5 0 5269 ymir.yggdrasil.li.
@@ -59,5 +56,3 @@ _infinoted._tcp IN SRV 5 0 6523 ymir.yggdrasil.li.
59_submission._tcp IN SRV 5 0 25 ymir.yggdrasil.li. 56_submission._tcp IN SRV 5 0 25 ymir.yggdrasil.li.
60_imap._tcp IN SRV 5 0 143 ymir.yggdrasil.li. 57_imap._tcp IN SRV 5 0 143 ymir.yggdrasil.li.
61_imaps._tcp IN SRV 5 0 993 ymir.yggdrasil.li. 58_imaps._tcp IN SRV 5 0 993 ymir.yggdrasil.li.
62
63_factorio._udp IN SRV 5 0 34197 game01.yggdrasil.li.
diff --git a/hosts/surtr/dns/zones/li.kleen.soa b/hosts/surtr/dns/zones/li.kleen.soa
index a1c7d35a..5dd3e697 100644
--- a/hosts/surtr/dns/zones/li.kleen.soa
+++ b/hosts/surtr/dns/zones/li.kleen.soa
@@ -1,7 +1,7 @@
1$ORIGIN kleen.li. 1$ORIGIN kleen.li.
2$TTL 3600 2$TTL 3600
3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li ( 3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li (
4 2023013000 ; serial 4 2025060701 ; serial
5 10800 ; refresh 5 10800 ; refresh
6 3600 ; retry 6 3600 ; retry
7 604800 ; expire 7 604800 ; expire
@@ -27,11 +27,8 @@ $TTL 3600
27 27
28_acme-challenge IN NS ns.yggdrasil.li. 28_acme-challenge IN NS ns.yggdrasil.li.
29 29
30ymir._domainkey IN TXT ( 30ymir._domainkey IN CNAME ymir._domainkey.yggdrasil.li.
31 "v=DKIM1;k=rsa;p=MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq3cCKlk+VPhyAanLZTM0BCzUT/+fmxHioZcFk0uJk1akBYj7BRofR7eVNcLKpm3rwYMQgE+9vJH9p8SV6tws9EcWc8SMCqqGZlREYM7PmLDiTSK/vjCzkygfgFCb0EBNsY2A/fpP4rTeoxrbcBSvMkq97iY5rwyw4wXZVZXLiDaCj23s8POoxTk1ClqUJZJQ5x2" 31surtr._domainkey IN CNAME surtr._domainkey.yggdrasil.li.
32 "qzrC0RfN5kLZ9A7Gq2jB09vNxpXHYqABA0bJv88JiZM7hfkp9IafJZ+yCVMaBcJs4DAxnTjNAuFD9gm+qSFVY8+yeXqL6Qjo5PbruhyZRBW8RgRYT8t5n07XRglMGKKGMwOGLanrltcyXqB+GsDZBD36RAAwjFadnxdpDyRv4SgRP7ff2tKRrORYpmpN+mKdqw5j3J/nP6bXV1oAkyh9XQkPEIDi81WT87EZziTElDzVp6A2qFOxqucAovoRk24"
33 "7vlsns1FApFRsp9mja0UZNObyKD1M6tP9Ep7lS76tFGMk+WDvXRJH5LEsyCpu7sSyl1r/O0M4K+KldRCqLlZd7rf8F5P8T0dn1azk05g7F4p0N/y9GNdzXbPZ9u0eZdI7SEdh8ZoOZp7NVZiBFfbWLSS5ZtyA2kbBa4i7GJ/cuAbEKOmqAkeQPiu96TGIcyjkXjS6mTPI+9UmKZYZC+OM8XdJ02y5KRoonCc19ZS8CAwEAAQ=="
34)
35 32
36_xmpp-client._tcp IN SRV 5 0 5222 ymir.yggdrasil.li. 33_xmpp-client._tcp IN SRV 5 0 5222 ymir.yggdrasil.li.
37_xmpp-server._tcp IN SRV 5 0 5269 ymir.yggdrasil.li. 34_xmpp-server._tcp IN SRV 5 0 5269 ymir.yggdrasil.li.
diff --git a/hosts/surtr/dns/zones/li.synapse.soa b/hosts/surtr/dns/zones/li.synapse.soa
index 086d4a85..247cf025 100644
--- a/hosts/surtr/dns/zones/li.synapse.soa
+++ b/hosts/surtr/dns/zones/li.synapse.soa
@@ -1,7 +1,7 @@
1$ORIGIN synapse.li. 1$ORIGIN synapse.li.
2$TTL 3600 2$TTL 3600
3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li ( 3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li (
4 2023092100 ; serial 4 2025060701 ; serial
5 10800 ; refresh 5 10800 ; refresh
6 3600 ; retry 6 3600 ; retry
7 604800 ; expire 7 604800 ; expire
diff --git a/hosts/surtr/dns/zones/li.xmpp.soa b/hosts/surtr/dns/zones/li.xmpp.soa
deleted file mode 100644
index a9e98fb4..00000000
--- a/hosts/surtr/dns/zones/li.xmpp.soa
+++ /dev/null
@@ -1,43 +0,0 @@
1$ORIGIN xmpp.li.
2$TTL 3600
3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li (
4 2023013000 ; serial
5 10800 ; refresh
6 3600 ; retry
7 604800 ; expire
8 3600 ; min TTL
9)
10 IN NS ns.yggdrasil.li.
11 IN NS ns.inwx.de.
12 IN NS ns2.inwx.de.
13 IN NS ns3.inwx.eu.
14
15@ IN CAA 128 issue "letsencrypt.org; validationmethods=dns-01"
16@ IN CAA 128 iodef "mailto:caa@yggdrasil.li"
17
18@ IN A 188.68.51.254
19@ IN AAAA 2a03:4000:6:d004::
20@ IN MX 0 ymir.yggdrasil.li.
21@ IN TXT "v=spf1 redirect=yggdrasil.li"
22
23* IN A 188.68.51.254
24* IN AAAA 2a03:4000:6:d004::
25* IN MX 0 ymir.yggdrasil.li.
26* IN TXT "v=spf1 redirect=yggdrasil.li"
27
28_acme-challenge IN NS ns.yggdrasil.li.
29
30ymir._domainkey IN TXT (
31 "v=DKIM1;k=rsa;p=MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq3cCKlk+VPhyAanLZTM0BCzUT/+fmxHioZcFk0uJk1akBYj7BRofR7eVNcLKpm3rwYMQgE+9vJH9p8SV6tws9EcWc8SMCqqGZlREYM7PmLDiTSK/vjCzkygfgFCb0EBNsY2A/fpP4rTeoxrbcBSvMkq97iY5rwyw4wXZVZXLiDaCj23s8POoxTk1ClqUJZJQ5x2"
32 "qzrC0RfN5kLZ9A7Gq2jB09vNxpXHYqABA0bJv88JiZM7hfkp9IafJZ+yCVMaBcJs4DAxnTjNAuFD9gm+qSFVY8+yeXqL6Qjo5PbruhyZRBW8RgRYT8t5n07XRglMGKKGMwOGLanrltcyXqB+GsDZBD36RAAwjFadnxdpDyRv4SgRP7ff2tKRrORYpmpN+mKdqw5j3J/nP6bXV1oAkyh9XQkPEIDi81WT87EZziTElDzVp6A2qFOxqucAovoRk24"
33 "7vlsns1FApFRsp9mja0UZNObyKD1M6tP9Ep7lS76tFGMk+WDvXRJH5LEsyCpu7sSyl1r/O0M4K+KldRCqLlZd7rf8F5P8T0dn1azk05g7F4p0N/y9GNdzXbPZ9u0eZdI7SEdh8ZoOZp7NVZiBFfbWLSS5ZtyA2kbBa4i7GJ/cuAbEKOmqAkeQPiu96TGIcyjkXjS6mTPI+9UmKZYZC+OM8XdJ02y5KRoonCc19ZS8CAwEAAQ=="
34)
35
36_xmpp-client._tcp IN SRV 5 0 5222 ymir.yggdrasil.li.
37_xmpp-server._tcp IN SRV 5 0 5269 ymir.yggdrasil.li.
38
39_infinoted._tcp IN SRV 5 0 6523 ymir.yggdrasil.li.
40
41_submission._tcp IN SRV 5 0 25 ymir.yggdrasil.li.
42_imap._tcp IN SRV 5 0 143 ymir.yggdrasil.li.
43_imaps._tcp IN SRV 5 0 993 ymir.yggdrasil.li.
diff --git a/hosts/surtr/dns/zones/li.yggdrasil.soa b/hosts/surtr/dns/zones/li.yggdrasil.soa
index 9af6232f..500194ae 100644
--- a/hosts/surtr/dns/zones/li.yggdrasil.soa
+++ b/hosts/surtr/dns/zones/li.yggdrasil.soa
@@ -1,7 +1,7 @@
1$ORIGIN yggdrasil.li. 1$ORIGIN yggdrasil.li.
2$TTL 3600 2$TTL 3600
3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li ( 3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li (
4 2025010300 ; serial 4 2025060700 ; serial
5 10800 ; refresh 5 10800 ; refresh
6 3600 ; retry 6 3600 ; retry
7 604800 ; expire 7 604800 ; expire
@@ -77,12 +77,46 @@ _acme-challenge.immich IN NS ns.yggdrasil.li.
77 77
78immich IN HTTPS 1 . alpn="h2,h3" ipv4hint="202.61.241.61" ipv6hint="2a03:4000:52:ada::" 78immich IN HTTPS 1 . alpn="h2,h3" ipv4hint="202.61.241.61" ipv6hint="2a03:4000:52:ada::"
79 79
80paperless IN A 202.61.241.61
81paperless IN AAAA 2a03:4000:52:ada::
82paperless IN MX 0 surtr.yggdrasil.li
83paperless IN TXT "v=spf1 redirect=surtr.yggdrasil.li"
84_acme-challenge.paperless IN NS ns.yggdrasil.li.
85
86paperless IN HTTPS 1 . alpn="h2,h3" ipv4hint="202.61.241.61" ipv6hint="2a03:4000:52:ada::"
87
88hledger IN A 202.61.241.61
89hledger IN AAAA 2a03:4000:52:ada::
90hledger IN MX 0 surtr.yggdrasil.li
91hledger IN TXT "v=spf1 redirect=surtr.yggdrasil.li"
92_acme-challenge.hledger IN NS ns.yggdrasil.li.
93
94hledger IN HTTPS 1 . alpn="h2,h3" ipv4hint="202.61.241.61" ipv6hint="2a03:4000:52:ada::"
95
96audiobookshelf IN A 202.61.241.61
97audiobookshelf IN AAAA 2a03:4000:52:ada::
98audiobookshelf IN MX 0 surtr.yggdrasil.li
99audiobookshelf IN TXT "v=spf1 redirect=surtr.yggdrasil.li"
100_acme-challenge.audiobookshelf IN NS ns.yggdrasil.li.
101
102audiobookshelf IN HTTPS 1 . alpn="h2,h3" ipv4hint="202.61.241.61" ipv6hint="2a03:4000:52:ada::"
103
104kimai IN A 202.61.241.61
105kimai IN AAAA 2a03:4000:52:ada::
106kimai IN MX 0 surtr.yggdrasil.li
107kimai IN TXT "v=spf1 redirect=surtr.yggdrasil.li"
108_acme-challenge.kimai IN NS ns.yggdrasil.li.
109
110kimai IN HTTPS 1 . alpn="h2,h3" ipv4hint="202.61.241.61" ipv6hint="2a03:4000:52:ada::"
111
80vidhar IN AAAA 2a03:4000:52:ada:4:1:: 112vidhar IN AAAA 2a03:4000:52:ada:4:1::
81vidhar IN MX 0 ymir.yggdrasil.li 113vidhar IN MX 0 ymir.yggdrasil.li
82vidhar IN TXT "v=spf1 redirect=yggdrasil.li" 114vidhar IN TXT "v=spf1 redirect=yggdrasil.li"
83 115
84mailout IN A 188.68.51.254 116mailout IN A 188.68.51.254
85mailout IN AAAA 2a03:4000:6:d004:: 117mailout IN AAAA 2a03:4000:6:d004::
118mailout IN A 202.61.241.61
119mailout IN AAAA 2a03:4000:52:ada::
86mailout IN MX 0 ymir.yggdrasil.li 120mailout IN MX 0 ymir.yggdrasil.li
87mailout IN TXT "v=spf1 redirect=yggdrasil.li" 121mailout IN TXT "v=spf1 redirect=yggdrasil.li"
88 122
@@ -104,6 +138,3 @@ _infinoted._tcp IN SRV 5 0 6523 ymir.yggdrasil.li.
104_submission._tcp IN SRV 5 0 25 ymir.yggdrasil.li. 138_submission._tcp IN SRV 5 0 25 ymir.yggdrasil.li.
105_imap._tcp IN SRV 5 0 143 ymir.yggdrasil.li. 139_imap._tcp IN SRV 5 0 143 ymir.yggdrasil.li.
106_imaps._tcp IN SRV 5 0 993 ymir.yggdrasil.li. 140_imaps._tcp IN SRV 5 0 993 ymir.yggdrasil.li.
107
108game01 IN A 94.16.107.151
109game01 IN AAAA 2a03:4000:50:13d:34ee:a2ff:fed0:328f
diff --git a/hosts/surtr/dns/zones/org.dirty-haskell.soa b/hosts/surtr/dns/zones/org.dirty-haskell.soa
deleted file mode 100644
index 27f0d7f9..00000000
--- a/hosts/surtr/dns/zones/org.dirty-haskell.soa
+++ /dev/null
@@ -1,34 +0,0 @@
1$ORIGIN dirty-haskell.org.
2$TTL 3600
3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li (
4 2023013000 ; serial
5 10800 ; refresh
6 3600 ; retry
7 604800 ; expire
8 3600 ; min TTL
9)
10 IN NS ns.yggdrasil.li.
11 IN NS ns.inwx.de.
12 IN NS ns2.inwx.de.
13 IN NS ns3.inwx.eu.
14
15@ IN CAA 128 issue "letsencrypt.org; validationmethods=dns-01"
16@ IN CAA 128 iodef "mailto:caa@yggdrasil.li"
17
18@ IN A 188.68.51.254
19@ IN AAAA 2a03:4000:6:d004::
20@ IN MX 10 ymir.yggdrasil.li.
21@ IN TXT "v=spf1 redirect=yggdrasil.li"
22
23* IN A 188.68.51.254
24* IN AAAA 2a03:4000:6:d004::
25* IN MX 0 ymir.yggdrasil.li.
26* IN TXT "v=spf1 redirect=yggdrasil.li"
27
28_acme-challenge IN NS ns.yggdrasil.li.
29
30ymir._domainkey IN TXT (
31 "v=DKIM1;k=rsa;p=MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq3cCKlk+VPhyAanLZTM0BCzUT/+fmxHioZcFk0uJk1akBYj7BRofR7eVNcLKpm3rwYMQgE+9vJH9p8SV6tws9EcWc8SMCqqGZlREYM7PmLDiTSK/vjCzkygfgFCb0EBNsY2A/fpP4rTeoxrbcBSvMkq97iY5rwyw4wXZVZXLiDaCj23s8POoxTk1ClqUJZJQ5x2"
32 "qzrC0RfN5kLZ9A7Gq2jB09vNxpXHYqABA0bJv88JiZM7hfkp9IafJZ+yCVMaBcJs4DAxnTjNAuFD9gm+qSFVY8+yeXqL6Qjo5PbruhyZRBW8RgRYT8t5n07XRglMGKKGMwOGLanrltcyXqB+GsDZBD36RAAwjFadnxdpDyRv4SgRP7ff2tKRrORYpmpN+mKdqw5j3J/nP6bXV1oAkyh9XQkPEIDi81WT87EZziTElDzVp6A2qFOxqucAovoRk24"
33 "7vlsns1FApFRsp9mja0UZNObyKD1M6tP9Ep7lS76tFGMk+WDvXRJH5LEsyCpu7sSyl1r/O0M4K+KldRCqLlZd7rf8F5P8T0dn1azk05g7F4p0N/y9GNdzXbPZ9u0eZdI7SEdh8ZoOZp7NVZiBFfbWLSS5ZtyA2kbBa4i7GJ/cuAbEKOmqAkeQPiu96TGIcyjkXjS6mTPI+9UmKZYZC+OM8XdJ02y5KRoonCc19ZS8CAwEAAQ=="
34)
diff --git a/hosts/surtr/dns/zones/org.praseodym.soa b/hosts/surtr/dns/zones/org.praseodym.soa
index df505b4c..2b97ca19 100644
--- a/hosts/surtr/dns/zones/org.praseodym.soa
+++ b/hosts/surtr/dns/zones/org.praseodym.soa
@@ -1,7 +1,7 @@
1$ORIGIN praseodym.org. 1$ORIGIN praseodym.org.
2$TTL 3600 2$TTL 3600
3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li ( 3@ IN SOA ns.yggdrasil.li. hostmaster.yggdrasil.li (
4 2023013000 ; serial 4 2025060701 ; serial
5 10800 ; refresh 5 10800 ; refresh
6 3600 ; retry 6 3600 ; retry
7 604800 ; expire 7 604800 ; expire
@@ -32,11 +32,8 @@ surtr IN AAAA 2a03:4000:52:ada::
32surtr IN MX 0 ymir.yggdrasil.li 32surtr IN MX 0 ymir.yggdrasil.li
33surtr IN TXT "v=spf1 redirect=yggdrasil.li" 33surtr IN TXT "v=spf1 redirect=yggdrasil.li"
34 34
35ymir._domainkey IN TXT ( 35ymir._domainkey IN CNAME ymir._domainkey.yggdrasil.li.
36 "v=DKIM1;k=rsa;p=MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq3cCKlk+VPhyAanLZTM0BCzUT/+fmxHioZcFk0uJk1akBYj7BRofR7eVNcLKpm3rwYMQgE+9vJH9p8SV6tws9EcWc8SMCqqGZlREYM7PmLDiTSK/vjCzkygfgFCb0EBNsY2A/fpP4rTeoxrbcBSvMkq97iY5rwyw4wXZVZXLiDaCj23s8POoxTk1ClqUJZJQ5x2" 36surtr._domainkey IN CNAME surtr._domainkey.yggdrasil.li.
37 "qzrC0RfN5kLZ9A7Gq2jB09vNxpXHYqABA0bJv88JiZM7hfkp9IafJZ+yCVMaBcJs4DAxnTjNAuFD9gm+qSFVY8+yeXqL6Qjo5PbruhyZRBW8RgRYT8t5n07XRglMGKKGMwOGLanrltcyXqB+GsDZBD36RAAwjFadnxdpDyRv4SgRP7ff2tKRrORYpmpN+mKdqw5j3J/nP6bXV1oAkyh9XQkPEIDi81WT87EZziTElDzVp6A2qFOxqucAovoRk24"
38 "7vlsns1FApFRsp9mja0UZNObyKD1M6tP9Ep7lS76tFGMk+WDvXRJH5LEsyCpu7sSyl1r/O0M4K+KldRCqLlZd7rf8F5P8T0dn1azk05g7F4p0N/y9GNdzXbPZ9u0eZdI7SEdh8ZoOZp7NVZiBFfbWLSS5ZtyA2kbBa4i7GJ/cuAbEKOmqAkeQPiu96TGIcyjkXjS6mTPI+9UmKZYZC+OM8XdJ02y5KRoonCc19ZS8CAwEAAQ=="
39)
40 37
41_xmpp-client._tcp IN SRV 5 0 5222 ymir.yggdrasil.li. 38_xmpp-client._tcp IN SRV 5 0 5222 ymir.yggdrasil.li.
42_xmpp-server._tcp IN SRV 5 0 5269 ymir.yggdrasil.li. 39_xmpp-server._tcp IN SRV 5 0 5269 ymir.yggdrasil.li.
diff --git a/hosts/surtr/email/ccert-policy-server/ccert_policy_server/__main__.py b/hosts/surtr/email/ccert-policy-server/ccert_policy_server/__main__.py
index 00182523..7c931559 100644
--- a/hosts/surtr/email/ccert-policy-server/ccert_policy_server/__main__.py
+++ b/hosts/surtr/email/ccert-policy-server/ccert_policy_server/__main__.py
@@ -28,10 +28,12 @@ class PolicyHandler(StreamRequestHandler):
28 28
29 allowed = False 29 allowed = False
30 user = None 30 user = None
31 relay_eligible = False
31 if self.args['sasl_username']: 32 if self.args['sasl_username']:
32 user = self.args['sasl_username'] 33 user = self.args['sasl_username']
33 if self.args['ccert_subject']: 34 if self.args['ccert_subject']:
34 user = self.args['ccert_subject'] 35 user = self.args['ccert_subject']
36 relay_eligible = True
35 37
36 if user: 38 if user:
37 with self.server.db_pool.connection() as conn: 39 with self.server.db_pool.connection() as conn:
@@ -44,10 +46,16 @@ class PolicyHandler(StreamRequestHandler):
44 46
45 with conn.cursor() as cur: 47 with conn.cursor() as cur:
46 cur.row_factory = namedtuple_row 48 cur.row_factory = namedtuple_row
47 cur.execute('SELECT "mailbox"."mailbox" as "user", "local", "extension", "domain" FROM "mailbox" INNER JOIN "mailbox_mapping" ON "mailbox".id = "mailbox_mapping"."mailbox" WHERE "mailbox"."mailbox" = %(user)s AND ("local" = %(local)s OR "local" IS NULL) AND ("extension" = %(extension)s OR "extension" IS NULL) AND "domain" = %(domain)s', params = {'user': user, 'local': local, 'extension': extension if extension is not None else '', 'domain': domain}, prepare=True) 49
48 for record in cur: 50 if relay_eligible:
49 logger.debug('Received result: %s', record) 51 cur.execute('SELECT EXISTS(SELECT true FROM "mailbox" INNER JOIN "relay_access" ON "mailbox".id = "relay_access"."mailbox" WHERE "mailbox"."mailbox" = %(user)s AND ("domain" = %(domain)s OR %(domain)s ilike CONCAT(\'%%_.\', "domain"))) as "exists"', params = {'user': user, 'domain': domain})
50 allowed = True 52 if (row := cur.fetchone()) is not None:
53 allowed = row.exists
54
55 if not allowed:
56 cur.execute('SELECT EXISTS(SELECT true FROM "mailbox" INNER JOIN "mailbox_mapping" ON "mailbox".id = "mailbox_mapping"."mailbox" WHERE "mailbox"."mailbox" = %(user)s AND ("local" = %(local)s OR "local" IS NULL) AND ("extension" = %(extension)s OR "extension" IS NULL) AND "domain" = %(domain)s) as "exists"', params = {'user': user, 'local': local, 'extension': extension if extension is not None else '', 'domain': domain}, prepare=True)
57 if (row := cur.fetchone()) is not None:
58 allowed = row.exists
51 59
52 action = '550 5.7.0 Sender address not authorized for current user' 60 action = '550 5.7.0 Sender address not authorized for current user'
53 if allowed: 61 if allowed:
diff --git a/hosts/surtr/email/default.nix b/hosts/surtr/email/default.nix
index 4196a8bc..fa7ddac6 100644
--- a/hosts/surtr/email/default.nix
+++ b/hosts/surtr/email/default.nix
@@ -1,4 +1,4 @@
1{ config, pkgs, lib, flakeInputs, ... }: 1{ config, pkgs, lib, flake, flakeInputs, ... }:
2 2
3with lib; 3with lib;
4 4
@@ -15,7 +15,7 @@ let
15 15
16 for file in $out/pipe/bin/*; do 16 for file in $out/pipe/bin/*; do
17 wrapProgram $file \ 17 wrapProgram $file \
18 --set PATH "${pkgs.coreutils}/bin:${pkgs.rspamd}/bin" 18 --set PATH "${makeBinPath (with pkgs; [coreutils rspamd])}"
19 done 19 done
20 ''; 20 '';
21 }; 21 };
@@ -33,12 +33,28 @@ let
33 }); 33 });
34 }); 34 });
35 }; 35 };
36 internal-policy-server =
37 let
38 workspace = flakeInputs.uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./internal-policy-server; };
39 pythonSet = flake.lib.pythonSet {
40 inherit pkgs;
41 python = pkgs.python312;
42 overlay = workspace.mkPyprojectOverlay {
43 sourcePreference = "wheel";
44 };
45 };
46 virtualEnv = pythonSet.mkVirtualEnv "internal-policy-server-env" workspace.deps.default;
47 in virtualEnv.overrideAttrs (oldAttrs: {
48 meta = (oldAttrs.meta or {}) // {
49 mainProgram = "internal-policy-server";
50 };
51 });
36 52
37 nftables-nologin-script = pkgs.writeScript "nftables-mail-nologin" '' 53 nftables-nologin-script = pkgs.resholve.writeScript "nftables-mail-nologin" {
38 #!${pkgs.zsh}/bin/zsh 54 inputs = with pkgs; [inetutils nftables gnugrep findutils];
39 55 interpreter = lib.getExe pkgs.zsh;
56 } ''
40 set -e 57 set -e
41 export PATH="${lib.makeBinPath (with pkgs; [inetutils nftables])}:$PATH"
42 58
43 typeset -a as_sets mnt_bys route route6 59 typeset -a as_sets mnt_bys route route6
44 as_sets=(${lib.escapeShellArgs config.services.email.nologin.ASSets}) 60 as_sets=(${lib.escapeShellArgs config.services.email.nologin.ASSets})
@@ -51,7 +67,7 @@ let
51 elif [[ "''${line}" =~ "^route6:\s+(.+)$" ]]; then 67 elif [[ "''${line}" =~ "^route6:\s+(.+)$" ]]; then
52 route6+=($match[1]) 68 route6+=($match[1])
53 fi 69 fi
54 done < <(whois -h whois.radb.net "!i''${as_set},1" | egrep -o 'AS[0-9]+' | xargs -- whois -h whois.radb.net -- -i origin) 70 done < <(whois -h whois.radb.net "!i''${as_set},1" | grep -Eo 'AS[0-9]+' | xargs whois -h whois.radb.net -- -i origin)
55 done 71 done
56 for mnt_by in $mnt_bys; do 72 for mnt_by in $mnt_bys; do
57 while IFS=$'\n' read line; do 73 while IFS=$'\n' read line; do
@@ -113,14 +129,14 @@ in {
113 setSendmail = true; 129 setSendmail = true;
114 postmasterAlias = ""; rootAlias = ""; extraAliases = ""; 130 postmasterAlias = ""; rootAlias = ""; extraAliases = "";
115 destination = []; 131 destination = [];
116 sslCert = "/run/credentials/postfix.service/surtr.yggdrasil.li.pem";
117 sslKey = "/run/credentials/postfix.service/surtr.yggdrasil.li.key.pem";
118 networks = []; 132 networks = [];
119 config = let 133 config = {
120 relay_ccert = "texthash:${pkgs.writeText "relay_ccert" ""}";
121 in {
122 smtpd_tls_security_level = "may"; 134 smtpd_tls_security_level = "may";
123 135
136 smtpd_tls_chain_files = [
137 "/run/credentials/postfix.service/surtr.yggdrasil.li.full.pem"
138 ];
139
124 #the dh params 140 #the dh params
125 smtpd_tls_dh1024_param_file = toString config.security.dhparams.params."postfix-1024".path; 141 smtpd_tls_dh1024_param_file = toString config.security.dhparams.params."postfix-1024".path;
126 smtpd_tls_dh512_param_file = toString config.security.dhparams.params."postfix-512".path; 142 smtpd_tls_dh512_param_file = toString config.security.dhparams.params."postfix-512".path;
@@ -155,12 +171,7 @@ in {
155 171
156 smtp_tls_connection_reuse = true; 172 smtp_tls_connection_reuse = true;
157 173
158 tls_server_sni_maps = ''texthash:${pkgs.writeText "sni" ( 174 tls_server_sni_maps = "inline:{${concatMapStringsSep ", " (domain: "{ ${domain} = /run/credentials/postfix.service/${removePrefix "." domain}.full.pem }") (concatMap (domain: [domain "mailin.${domain}" "mailsub.${domain}" ".${domain}"]) emailDomains)}}";
159 concatMapStringsSep "\n\n" (domain:
160 concatMapStringsSep "\n" (subdomain: "${subdomain} /run/credentials/postfix.service/${removePrefix "." subdomain}.full.pem")
161 [domain "mailin.${domain}" "mailsub.${domain}" ".${domain}"]
162 ) emailDomains
163 )}'';
164 175
165 smtp_tls_policy_maps = "socketmap:unix:${config.services.postfix-mta-sts-resolver.settings.path}:postfix"; 176 smtp_tls_policy_maps = "socketmap:unix:${config.services.postfix-mta-sts-resolver.settings.path}:postfix";
166 177
@@ -184,25 +195,26 @@ in {
184 dbname = email 195 dbname = email
185 query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s' 196 query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s'
186 ''}" 197 ''}"
187 "check_ccert_access ${relay_ccert}"
188 "reject_non_fqdn_helo_hostname" 198 "reject_non_fqdn_helo_hostname"
189 "reject_invalid_helo_hostname" 199 "reject_invalid_helo_hostname"
190 "reject_unauth_destination" 200 "reject_unauth_destination"
191 "reject_unknown_recipient_domain" 201 "reject_unknown_recipient_domain"
192 "reject_unverified_recipient" 202 "reject_unverified_recipient"
203 "check_policy_service unix:/run/postfix-internal-policy.sock"
193 ]; 204 ];
194 unverified_recipient_reject_code = "550"; 205 unverified_recipient_reject_code = "550";
195 unverified_recipient_reject_reason = "Recipient address lookup failed"; 206 unverified_recipient_reject_reason = "Recipient address lookup failed";
196 address_verify_map = "internal:address_verify_map"; 207 address_verify_map = "internal:address_verify_map";
197 address_verify_positive_expire_time = "1h"; 208 address_verify_positive_expire_time = "1h";
198 address_verify_positive_refresh_time = "15m"; 209 address_verify_positive_refresh_time = "15m";
199 address_verify_negative_expire_time = "15s"; 210 address_verify_negative_expire_time = "5m";
200 address_verify_negative_refresh_time = "5s"; 211 address_verify_negative_refresh_time = "1m";
201 address_verify_cache_cleanup_interval = "5s"; 212 address_verify_cache_cleanup_interval = "12h";
213 address_verify_poll_count = "\${stress?15}\${stress:30}";
202 address_verify_poll_delay = "1s"; 214 address_verify_poll_delay = "1s";
215 address_verify_sender_ttl = "30045s";
203 216
204 smtpd_relay_restrictions = [ 217 smtpd_relay_restrictions = [
205 "check_ccert_access ${relay_ccert}"
206 "reject_unauth_destination" 218 "reject_unauth_destination"
207 ]; 219 ];
208 220
@@ -213,7 +225,7 @@ in {
213 smtpd_client_event_limit_exceptions = ""; 225 smtpd_client_event_limit_exceptions = "";
214 226
215 milter_default_action = "accept"; 227 milter_default_action = "accept";
216 smtpd_milters = [config.services.opendkim.socket "local:/run/rspamd/rspamd-milter.sock"]; 228 smtpd_milters = [config.services.opendkim.socket "local:/run/rspamd/rspamd-milter.sock" "local:/run/postsrsd/postsrsd-milter.sock"];
217 non_smtpd_milters = [config.services.opendkim.socket "local:/run/rspamd/rspamd-milter.sock"]; 229 non_smtpd_milters = [config.services.opendkim.socket "local:/run/rspamd/rspamd-milter.sock"];
218 230
219 alias_maps = ""; 231 alias_maps = "";
@@ -235,11 +247,6 @@ in {
235 ::/0 silent-discard, dsn 247 ::/0 silent-discard, dsn
236 ''}"; 248 ''}";
237 249
238 sender_canonical_maps = "tcp:localhost:${toString config.services.postsrsd.forwardPort}";
239 sender_canonical_classes = "envelope_sender";
240 recipient_canonical_maps = "tcp:localhost:${toString config.services.postsrsd.reversePort}";
241 recipient_canonical_classes = ["envelope_recipient" "header_recipient"];
242
243 virtual_mailbox_domains = ''pgsql:${pkgs.writeText "virtual_mailbox_domains.cf" '' 250 virtual_mailbox_domains = ''pgsql:${pkgs.writeText "virtual_mailbox_domains.cf" ''
244 hosts = postgresql:///email 251 hosts = postgresql:///email
245 dbname = email 252 dbname = email
@@ -254,11 +261,24 @@ in {
254 virtual_transport = "dvlmtp:unix:/run/dovecot-lmtp"; 261 virtual_transport = "dvlmtp:unix:/run/dovecot-lmtp";
255 smtputf8_enable = false; 262 smtputf8_enable = false;
256 263
257 authorized_submit_users = "inline:{ root= postfwd= }"; 264 authorized_submit_users = "inline:{ root= postfwd= ${config.services.dovecot2.user}= }";
265 authorized_flush_users = "inline:{ root= }";
266 authorized_mailq_users = "inline:{ root= }";
258 267
259 postscreen_access_list = ""; 268 postscreen_access_list = "";
260 postscreen_denylist_action = "drop"; 269 postscreen_denylist_action = "drop";
261 postscreen_greet_action = "enforce"; 270 postscreen_greet_action = "enforce";
271
272 sender_bcc_maps = ''pgsql:${pkgs.writeText "sender_bcc_maps.cf" ''
273 hosts = postgresql:///email
274 dbname = email
275 query = SELECT value FROM sender_bcc_maps WHERE key = '%s'
276 ''}'';
277 recipient_bcc_maps = ''pgsql:${pkgs.writeText "recipient_bcc_maps.cf" ''
278 hosts = postgresql:///email
279 dbname = email
280 query = SELECT value FROM recipient_bcc_maps WHERE key = '%s'
281 ''}'';
262 }; 282 };
263 masterConfig = { 283 masterConfig = {
264 "465" = { 284 "465" = {
@@ -283,7 +303,7 @@ in {
283 hosts = postgresql:///email 303 hosts = postgresql:///email
284 dbname = email 304 dbname = email
285 query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s' OR (lookup = regexp_replace('%s', '\+[^@]*@', '@') AND NOT EXISTS (SELECT 1 FROM virtual_mailbox_access WHERE lookup = '%s')) 305 query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s' OR (lookup = regexp_replace('%s', '\+[^@]*@', '@') AND NOT EXISTS (SELECT 1 FROM virtual_mailbox_access WHERE lookup = '%s'))
286 ''},permit_tls_all_clientcerts,reject}'' 306 ''},check_policy_service unix:/run/postfix-internal-policy.sock,permit_tls_all_clientcerts,reject}''
287 "-o" "smtpd_relay_restrictions=permit_tls_all_clientcerts,reject" 307 "-o" "smtpd_relay_restrictions=permit_tls_all_clientcerts,reject"
288 "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}" 308 "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}"
289 "-o" "unverified_sender_reject_code=550" 309 "-o" "unverified_sender_reject_code=550"
@@ -313,7 +333,7 @@ in {
313 hosts = postgresql:///email 333 hosts = postgresql:///email
314 dbname = email 334 dbname = email
315 query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s' OR (lookup = regexp_replace('%s', '\+[^@]*@', '@') AND NOT EXISTS (SELECT 1 FROM virtual_mailbox_access WHERE lookup = '%s')) 335 query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s' OR (lookup = regexp_replace('%s', '\+[^@]*@', '@') AND NOT EXISTS (SELECT 1 FROM virtual_mailbox_access WHERE lookup = '%s'))
316 ''},permit_sasl_authenticated,reject}'' 336 ''},check_policy_service unix:/run/postfix-internal-policy.sock,permit_sasl_authenticated,reject}''
317 "-o" "smtpd_relay_restrictions=permit_sasl_authenticated,reject" 337 "-o" "smtpd_relay_restrictions=permit_sasl_authenticated,reject"
318 "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}" 338 "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}"
319 "-o" "unverified_sender_reject_code=550" 339 "-o" "unverified_sender_reject_code=550"
@@ -328,7 +348,10 @@ in {
328 maxproc = 0; 348 maxproc = 0;
329 args = [ 349 args = [
330 "-o" "header_checks=pcre:${pkgs.writeText "header_checks_submission" '' 350 "-o" "header_checks=pcre:${pkgs.writeText "header_checks_submission" ''
351 if /^Received: /
352 !/by surtr\.yggdrasil\.li/ STRIP
331 /^Received: from [^ ]+ \([^ ]+ [^ ]+\)\s+(.*)$/ REPLACE Received: $1 353 /^Received: from [^ ]+ \([^ ]+ [^ ]+\)\s+(.*)$/ REPLACE Received: $1
354 endif
332 ''}" 355 ''}"
333 ]; 356 ];
334 }; 357 };
@@ -364,17 +387,19 @@ in {
364 387
365 services.postsrsd = { 388 services.postsrsd = {
366 enable = true; 389 enable = true;
367 domain = "surtr.yggdrasil.li"; 390 domains = [ "surtr.yggdrasil.li" ] ++ concatMap (domain: [".${domain}" domain]) emailDomains;
368 separator = "+"; 391 separator = "+";
369 excludeDomains = [ "surtr.yggdrasil.li" 392 extraConfig = ''
370 ] ++ concatMap (domain: [".${domain}" domain]) emailDomains; 393 socketmap = unix:/run/postsrsd/postsrsd-socketmap.sock
394 milter = unix:/run/postsrsd/postsrsd-milter.sock
395 '';
371 }; 396 };
372 397
373 services.opendkim = { 398 services.opendkim = {
374 enable = true; 399 enable = true;
375 user = "postfix"; group = "postfix"; 400 user = "postfix"; group = "postfix";
376 socket = "local:/run/opendkim/opendkim.sock"; 401 socket = "local:/run/opendkim/opendkim.sock";
377 domains = ''csl:${concatStringsSep "," (["surtr.yggdrasil.li"] ++ emailDomains)}''; 402 domains = ''csl:${concatStringsSep "," (["surtr.yggdrasil.li" "yggdrasil.li" "141.li" "kleen.li" "synapse.li" "praseodym.org"] ++ emailDomains)}'';
378 selector = "surtr"; 403 selector = "surtr";
379 configFile = builtins.toFile "opendkim.conf" '' 404 configFile = builtins.toFile "opendkim.conf" ''
380 Syslog true 405 Syslog true
@@ -478,20 +503,20 @@ in {
478 }; 503 };
479 }; 504 };
480 505
481 users.groups.${config.services.rspamd.group}.members = [ config.services.postfix.user "dovecot2" ]; 506 users.groups.${config.services.rspamd.group}.members = [ config.services.postfix.user config.services.dovecot2.user ];
482 507
483 services.redis.servers.rspamd.enable = true; 508 services.redis.servers.rspamd.enable = true;
484 509
485 users.groups.${config.services.redis.servers.rspamd.user}.members = [ config.services.rspamd.user ]; 510 users.groups.${config.services.redis.servers.rspamd.user}.members = [ config.services.rspamd.user ];
486 511
512 environment.systemPackages = with pkgs; [ dovecot_pigeonhole dovecot_fts_xapian ];
487 services.dovecot2 = { 513 services.dovecot2 = {
488 enable = true; 514 enable = true;
489 enablePAM = false; 515 enablePAM = false;
490 sslServerCert = "/run/credentials/dovecot2.service/surtr.yggdrasil.li.pem"; 516 sslServerCert = "/run/credentials/dovecot.service/surtr.yggdrasil.li.pem";
491 sslServerKey = "/run/credentials/dovecot2.service/surtr.yggdrasil.li.key.pem"; 517 sslServerKey = "/run/credentials/dovecot.service/surtr.yggdrasil.li.key.pem";
492 sslCACert = toString ./ca/ca.crt; 518 sslCACert = toString ./ca/ca.crt;
493 mailLocation = "maildir:/var/lib/mail/%u/maildir:UTF-8:INDEX=/var/lib/dovecot/indices/%u"; 519 mailLocation = "maildir:/var/lib/mail/%u/maildir:UTF-8:INDEX=/var/lib/dovecot/indices/%u";
494 modules = with pkgs; [ dovecot_pigeonhole dovecot_fts_xapian ];
495 mailPlugins.globally.enable = [ "fts" "fts_xapian" ]; 520 mailPlugins.globally.enable = [ "fts" "fts_xapian" ];
496 protocols = [ "lmtp" "sieve" ]; 521 protocols = [ "lmtp" "sieve" ];
497 sieve = { 522 sieve = {
@@ -502,8 +527,8 @@ in {
502 dovecotSqlConf = pkgs.writeText "dovecot-sql.conf" '' 527 dovecotSqlConf = pkgs.writeText "dovecot-sql.conf" ''
503 driver = pgsql 528 driver = pgsql
504 connect = dbname=email 529 connect = dbname=email
505 password_query = SELECT (CASE WHEN '%k' = 'valid' AND '%m' = 'EXTERNAL' THEN NULL ELSE "password" END) as password, (CASE WHEN '%k' = 'valid' AND '%m' = 'EXTERNAL' THEN true WHEN password IS NULL THEN true ELSE NULL END) as nopassword, "user", quota_rule, 'dovecot2' as uid, 'dovecot2' as gid FROM imap_user WHERE "user" = '%n' 530 password_query = SELECT (CASE WHEN '%k' = 'valid' AND '%m' = 'EXTERNAL' THEN NULL ELSE "password" END) as password, (CASE WHEN '%k' = 'valid' AND '%m' = 'EXTERNAL' THEN true WHEN password IS NULL THEN true ELSE NULL END) as nopassword, "user", quota_rule, '${config.services.dovecot2.user}' as uid, '${config.services.dovecot2.group}' as gid FROM imap_user WHERE "user" = '%n'
506 user_query = SELECT "user", quota_rule, 'dovecot2' as uid, 'dovecot2' as gid FROM imap_user WHERE "user" = '%n' 531 user_query = SELECT "user", quota_rule, '${config.services.dovecot2.user}' as uid, 'dovecot2' as gid FROM imap_user WHERE "user" = '%n'
507 iterate_query = SELECT "user" FROM imap_user 532 iterate_query = SELECT "user" FROM imap_user
508 ''; 533 '';
509 in '' 534 in ''
@@ -511,16 +536,16 @@ in {
511 536
512 mail_plugins = $mail_plugins quota 537 mail_plugins = $mail_plugins quota
513 538
514 first_valid_uid = ${toString config.users.users.dovecot2.uid} 539 first_valid_uid = ${toString config.users.users.${config.services.dovecot2.user}.uid}
515 last_valid_uid = ${toString config.users.users.dovecot2.uid} 540 last_valid_uid = ${toString config.users.users.${config.services.dovecot2.user}.uid}
516 first_valid_gid = ${toString config.users.groups.dovecot2.gid} 541 first_valid_gid = ${toString config.users.groups.${config.services.dovecot2.group}.gid}
517 last_valid_gid = ${toString config.users.groups.dovecot2.gid} 542 last_valid_gid = ${toString config.users.groups.${config.services.dovecot2.group}.gid}
518 543
519 ${concatMapStringsSep "\n\n" (domain: 544 ${concatMapStringsSep "\n\n" (domain:
520 concatMapStringsSep "\n" (subdomain: '' 545 concatMapStringsSep "\n" (subdomain: ''
521 local_name ${subdomain} { 546 local_name ${subdomain} {
522 ssl_cert = </run/credentials/dovecot2.service/${subdomain}.pem 547 ssl_cert = </run/credentials/dovecot.service/${subdomain}.pem
523 ssl_key = </run/credentials/dovecot2.service/${subdomain}.key.pem 548 ssl_key = </run/credentials/dovecot.service/${subdomain}.key.pem
524 } 549 }
525 '') ["imap.${domain}" domain] 550 '') ["imap.${domain}" domain]
526 ) emailDomains} 551 ) emailDomains}
@@ -541,10 +566,10 @@ in {
541 auth_debug = yes 566 auth_debug = yes
542 567
543 service auth { 568 service auth {
544 user = dovecot2 569 user = ${config.services.dovecot2.user}
545 } 570 }
546 service auth-worker { 571 service auth-worker {
547 user = dovecot2 572 user = ${config.services.dovecot2.user}
548 } 573 }
549 574
550 userdb { 575 userdb {
@@ -565,7 +590,7 @@ in {
565 args = ${pkgs.writeText "dovecot-sql.conf" '' 590 args = ${pkgs.writeText "dovecot-sql.conf" ''
566 driver = pgsql 591 driver = pgsql
567 connect = dbname=email 592 connect = dbname=email
568 user_query = SELECT DISTINCT ON (extension IS NULL, local IS NULL) "user", quota_rule, 'dovecot2' as uid, 'dovecot2' as gid FROM lmtp_mapping WHERE CASE WHEN extension IS NOT NULL AND local IS NOT NULL THEN ('%n' :: citext) = local || '+' || extension AND domain = ('%d' :: citext) WHEN local IS NOT NULL THEN (local = ('%n' :: citext) OR ('%n' :: citext) ILIKE local || '+%%') AND domain = ('%d' :: citext) WHEN extension IS NOT NULL THEN ('%n' :: citext) ILIKE '%%+' || extension AND domain = ('%d' :: citext) ELSE domain = ('%d' :: citext) END ORDER BY (extension IS NULL) ASC, (local IS NULL) ASC 593 user_query = SELECT DISTINCT ON (extension IS NULL, local IS NULL) "user", quota_rule, '${config.services.dovecot2.user}' as uid, '${config.services.dovecot2.group}' as gid FROM lmtp_mapping WHERE CASE WHEN extension IS NOT NULL AND local IS NOT NULL THEN ('%n' :: citext) = local || '+' || extension AND domain = ('%d' :: citext) WHEN local IS NOT NULL THEN (local = ('%n' :: citext) OR ('%n' :: citext) ILIKE local || '+%%') AND domain = ('%d' :: citext) WHEN extension IS NOT NULL THEN ('%n' :: citext) ILIKE '%%+' || extension AND domain = ('%d' :: citext) ELSE domain = ('%d' :: citext) END ORDER BY (extension IS NULL) ASC, (local IS NULL) ASC
569 ''} 594 ''}
570 595
571 skip = never 596 skip = never
@@ -673,7 +698,7 @@ in {
673 plugin { 698 plugin {
674 plugin = fts fts_xapian 699 plugin = fts fts_xapian
675 fts = xapian 700 fts = xapian
676 fts_xapian = partial=2 full=20 attachments=1 verbose=1 701 fts_xapian = partial=3 full=20 attachments=1 verbose=1
677 702
678 fts_autoindex = yes 703 fts_autoindex = yes
679 704
@@ -688,12 +713,12 @@ in {
688 713
689 systemd.services.dovecot-fts-xapian-optimize = { 714 systemd.services.dovecot-fts-xapian-optimize = {
690 description = "Optimize dovecot indices for fts_xapian"; 715 description = "Optimize dovecot indices for fts_xapian";
691 requisite = [ "dovecot2.service" ]; 716 requisite = [ "dovecot.service" ];
692 after = [ "dovecot2.service" ]; 717 after = [ "dovecot.service" ];
693 startAt = "*-*-* 22:00:00 Europe/Berlin"; 718 startAt = "*-*-* 22:00:00 Europe/Berlin";
694 serviceConfig = { 719 serviceConfig = {
695 Type = "oneshot"; 720 Type = "oneshot";
696 ExecStart = "${pkgs.dovecot}/bin/doveadm fts optimize -A"; 721 ExecStart = "${getExe' pkgs.dovecot "doveadm"} fts optimize -A";
697 PrivateDevices = true; 722 PrivateDevices = true;
698 PrivateNetwork = true; 723 PrivateNetwork = true;
699 ProtectKernelTunables = true; 724 ProtectKernelTunables = true;
@@ -754,31 +779,29 @@ in {
754 779
755 security.acme.rfc2136Domains = { 780 security.acme.rfc2136Domains = {
756 "surtr.yggdrasil.li" = { 781 "surtr.yggdrasil.li" = {
757 restartUnits = [ "postfix.service" "dovecot2.service" ]; 782 restartUnits = [ "postfix.service" "dovecot.service" ];
758 }; 783 };
759 } // listToAttrs (map (domain: nameValuePair "spm.${domain}" { restartUnits = ["nginx.service"]; }) spmDomains) 784 } // listToAttrs (map (domain: nameValuePair "spm.${domain}" { restartUnits = ["nginx.service"]; }) spmDomains)
760 // listToAttrs (concatMap (domain: [ 785 // listToAttrs (concatMap (domain: [
761 (nameValuePair domain { restartUnits = ["postfix.service" "dovecot2.service"]; }) 786 (nameValuePair domain { restartUnits = ["postfix.service" "dovecot.service"]; })
762 (nameValuePair "mailin.${domain}" { restartUnits = ["postfix.service"]; }) 787 (nameValuePair "mailin.${domain}" { restartUnits = ["postfix.service"]; })
763 (nameValuePair "mailsub.${domain}" { restartUnits = ["postfix.service"]; }) 788 (nameValuePair "mailsub.${domain}" { restartUnits = ["postfix.service"]; })
764 (nameValuePair "imap.${domain}" { restartUnits = ["dovecot2.service"]; }) 789 (nameValuePair "imap.${domain}" { restartUnits = ["dovecot.service"]; })
765 (nameValuePair "mta-sts.${domain}" { restartUnits = ["nginx.service"]; }) 790 (nameValuePair "mta-sts.${domain}" { restartUnits = ["nginx.service"]; })
766 ]) emailDomains); 791 ]) emailDomains);
767 792
768 systemd.services.postfix = { 793 systemd.services.postfix = {
769 serviceConfig.LoadCredential = [ 794 serviceConfig.LoadCredential = let
770 "surtr.yggdrasil.li.key.pem:${config.security.acme.certs."surtr.yggdrasil.li".directory}/key.pem" 795 tlsCredential = domain: "${domain}.full.pem:${config.security.acme.certs.${domain}.directory}/full.pem";
771 "surtr.yggdrasil.li.pem:${config.security.acme.certs."surtr.yggdrasil.li".directory}/fullchain.pem" 796 in [
772 ] ++ concatMap (domain: 797 (tlsCredential "surtr.yggdrasil.li")
773 map (subdomain: "${subdomain}.full.pem:${config.security.acme.certs.${subdomain}.directory}/full.pem") 798 ] ++ concatMap (domain: map tlsCredential [domain "mailin.${domain}" "mailsub.${domain}"]) emailDomains;
774 [domain "mailin.${domain}" "mailsub.${domain}"]
775 ) emailDomains;
776 }; 799 };
777 800
778 systemd.services.dovecot2 = { 801 systemd.services.dovecot = {
779 preStart = '' 802 preStart = ''
780 for f in /etc/dovecot/sieve_flag.d/*.sieve /etc/dovecot/sieve_before.d/*.sieve; do 803 for f in /etc/dovecot/sieve_flag.d/*.sieve /etc/dovecot/sieve_before.d/*.sieve; do
781 ${pkgs.dovecot_pigeonhole}/bin/sievec $f 804 ${getExe' pkgs.dovecot_pigeonhole "sievec"} $f
782 done 805 done
783 ''; 806 '';
784 807
@@ -845,15 +868,16 @@ in {
845 charset utf-8; 868 charset utf-8;
846 source_charset utf-8; 869 source_charset utf-8;
847 ''; 870 '';
848 root = pkgs.runCommand "mta-sts.${domain}" {} '' 871 root = pkgs.writeTextFile {
849 mkdir -p $out/.well-known 872 name = "mta-sts.${domain}";
850 cp ${pkgs.writeText "mta-sts.${domain}.txt" '' 873 destination = "/.well-known/mta-sts.txt";
874 text = ''
851 version: STSv1 875 version: STSv1
852 mode: enforce 876 mode: enforce
853 max_age: 2419200 877 max_age: 2419200
854 mx: mailin.${domain} 878 mx: mailin.${domain}
855 ''} $out/.well-known/mta-sts.txt 879 '';
856 ''; 880 };
857 }; 881 };
858 }) emailDomains); 882 }) emailDomains);
859 }; 883 };
@@ -870,7 +894,7 @@ in {
870 systemd.services.spm = { 894 systemd.services.spm = {
871 serviceConfig = { 895 serviceConfig = {
872 Type = "notify"; 896 Type = "notify";
873 ExecStart = "${pkgs.spm}/bin/spm-server"; 897 ExecStart = getExe' pkgs.spm "spm-server";
874 User = "spm"; 898 User = "spm";
875 Group = "spm"; 899 Group = "spm";
876 900
@@ -928,7 +952,7 @@ in {
928 serviceConfig = { 952 serviceConfig = {
929 Type = "notify"; 953 Type = "notify";
930 954
931 ExecStart = "${ccert-policy-server}/bin/ccert-policy-server"; 955 ExecStart = getExe' ccert-policy-server "ccert-policy-server";
932 956
933 Environment = [ 957 Environment = [
934 "PGDATABASE=email" 958 "PGDATABASE=email"
@@ -961,6 +985,53 @@ in {
961 }; 985 };
962 users.groups."postfix-ccert-sender-policy" = {}; 986 users.groups."postfix-ccert-sender-policy" = {};
963 987
988 systemd.sockets."postfix-internal-policy" = {
989 requiredBy = ["postfix.service"];
990 wants = ["postfix-internal-policy.service"];
991 socketConfig = {
992 ListenStream = "/run/postfix-internal-policy.sock";
993 };
994 };
995 systemd.services."postfix-internal-policy" = {
996 after = [ "postgresql.service" ];
997 bindsTo = [ "postgresql.service" ];
998
999 serviceConfig = {
1000 Type = "notify";
1001
1002 ExecStart = lib.getExe internal-policy-server;
1003
1004 Environment = [
1005 "PGDATABASE=email"
1006 ];
1007
1008 DynamicUser = false;
1009 User = "postfix-internal-policy";
1010 Group = "postfix-internal-policy";
1011 ProtectSystem = "strict";
1012 SystemCallFilter = "@system-service";
1013 NoNewPrivileges = true;
1014 ProtectKernelTunables = true;
1015 ProtectKernelModules = true;
1016 ProtectKernelLogs = true;
1017 ProtectControlGroups = true;
1018 MemoryDenyWriteExecute = true;
1019 RestrictSUIDSGID = true;
1020 KeyringMode = "private";
1021 ProtectClock = true;
1022 RestrictRealtime = true;
1023 PrivateDevices = true;
1024 PrivateTmp = true;
1025 ProtectHostname = true;
1026 ReadWritePaths = ["/run/postgresql"];
1027 };
1028 };
1029 users.users."postfix-internal-policy" = {
1030 isSystemUser = true;
1031 group = "postfix-internal-policy";
1032 };
1033 users.groups."postfix-internal-policy" = {};
1034
964 services.postfwd = { 1035 services.postfwd = {
965 enable = true; 1036 enable = true;
966 cache = false; 1037 cache = false;
diff --git a/hosts/surtr/email/internal-policy-server/.envrc b/hosts/surtr/email/internal-policy-server/.envrc
new file mode 100644
index 00000000..2c909235
--- /dev/null
+++ b/hosts/surtr/email/internal-policy-server/.envrc
@@ -0,0 +1,4 @@
1use flake
2
3[[ -d ".venv" ]] || ( uv venv && uv sync )
4. .venv/bin/activate
diff --git a/hosts/surtr/email/internal-policy-server/.gitignore b/hosts/surtr/email/internal-policy-server/.gitignore
new file mode 100644
index 00000000..4ccfae70
--- /dev/null
+++ b/hosts/surtr/email/internal-policy-server/.gitignore
@@ -0,0 +1,2 @@
1.venv
2**/__pycache__
diff --git a/hosts/surtr/email/internal-policy-server/internal_policy_server/__init__.py b/hosts/surtr/email/internal-policy-server/internal_policy_server/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/hosts/surtr/email/internal-policy-server/internal_policy_server/__init__.py
diff --git a/hosts/surtr/email/internal-policy-server/internal_policy_server/__main__.py b/hosts/surtr/email/internal-policy-server/internal_policy_server/__main__.py
new file mode 100644
index 00000000..04f1a59a
--- /dev/null
+++ b/hosts/surtr/email/internal-policy-server/internal_policy_server/__main__.py
@@ -0,0 +1,106 @@
1from systemd.daemon import listen_fds
2from sdnotify import SystemdNotifier
3from socketserver import StreamRequestHandler, ThreadingMixIn
4from systemd_socketserver import SystemdSocketServer
5import sys
6from threading import Thread
7from psycopg_pool import ConnectionPool
8from psycopg.rows import namedtuple_row
9
10import logging
11
12
13class PolicyHandler(StreamRequestHandler):
14 def handle(self):
15 logger.debug('Handling new connection...')
16
17 self.args = dict()
18
19 line = None
20 while line := self.rfile.readline().removesuffix(b'\n'):
21 if b'=' not in line:
22 break
23
24 key, val = line.split(sep=b'=', maxsplit=1)
25 self.args[key.decode()] = val.decode()
26
27 logger.info('Connection parameters: %s', self.args)
28
29 allowed = False
30 user = None
31 if self.args['sasl_username']:
32 user = self.args['sasl_username']
33 if self.args['ccert_subject']:
34 user = self.args['ccert_subject']
35
36 with self.server.db_pool.connection() as conn:
37 local, domain = self.args['recipient'].split(sep='@', maxsplit=1)
38 extension = None
39 if '+' in local:
40 local, extension = local.split(sep='+', maxsplit=1)
41
42 logger.debug('Parsed recipient address: %s', {'local': local, 'extension': extension, 'domain': domain})
43
44 with conn.cursor() as cur:
45 cur.row_factory = namedtuple_row
46 cur.execute('SELECT id, internal FROM "mailbox_mapping" WHERE ("local" = %(local)s OR "local" IS NULL) AND ("extension" = %(extension)s OR "extension" IS NULL) AND "domain" = %(domain)s', params = {'local': local, 'extension': extension if extension is not None else '', 'domain': domain}, prepare = True)
47 if (row := cur.fetchone()) is not None:
48 if not row.internal:
49 logger.debug('Recipient mailbox is not internal')
50 allowed = True
51 elif user:
52 cur.execute('SELECT EXISTS(SELECT true FROM "mailbox_mapping_access" INNER JOIN "mailbox" ON "mailbox".id = "mailbox_mapping_access"."mailbox" WHERE mailbox_mapping = %(mailbox_mapping)s AND "mailbox"."mailbox" = %(user)s) as "exists"', params = { 'mailbox_mapping': row.id, 'user': user }, prepare = True)
53 if (row := cur.fetchone()) is not None:
54 allowed = row.exists
55 else:
56 logger.debug('Recipient is not local')
57 allowed = True
58
59 action = '550 5.7.0 Recipient mailbox mapping not authorized for current user'
60 if allowed:
61 action = 'DUNNO'
62
63 logger.info('Reached verdict: %s', {'allowed': allowed, 'action': action})
64 self.wfile.write(f'action={action}\n\n'.encode())
65
66class ThreadedSystemdSocketServer(ThreadingMixIn, SystemdSocketServer):
67 def __init__(self, fd, RequestHandlerClass):
68 super().__init__(fd, RequestHandlerClass)
69
70 self.db_pool = ConnectionPool(min_size=1)
71 self.db_pool.wait()
72
73def main():
74 global logger
75 logger = logging.getLogger(__name__)
76 console_handler = logging.StreamHandler()
77 console_handler.setFormatter( logging.Formatter('[%(levelname)s](%(name)s): %(message)s') )
78 if sys.stderr.isatty():
79 console_handler.setFormatter( logging.Formatter('%(asctime)s [%(levelname)s](%(name)s): %(message)s') )
80 logger.addHandler(console_handler)
81 logger.setLevel(logging.DEBUG)
82
83 # log uncaught exceptions
84 def log_exceptions(type, value, tb):
85 global logger
86
87 logger.error(value)
88 sys.__excepthook__(type, value, tb) # calls default excepthook
89
90 sys.excepthook = log_exceptions
91
92 fds = listen_fds()
93 servers = [ThreadedSystemdSocketServer(fd, PolicyHandler) for fd in fds]
94
95 if servers:
96 for server in servers:
97 Thread(name=f'Server for fd{server.fileno()}', target=server.serve_forever).start()
98 else:
99 return 2
100
101 SystemdNotifier().notify('READY=1')
102
103 return 0
104
105if __name__ == '__main__':
106 sys.exit(main())
diff --git a/hosts/surtr/email/internal-policy-server/pyproject.toml b/hosts/surtr/email/internal-policy-server/pyproject.toml
new file mode 100644
index 00000000..c697cd01
--- /dev/null
+++ b/hosts/surtr/email/internal-policy-server/pyproject.toml
@@ -0,0 +1,18 @@
1[project]
2name = "internal-policy-server"
3version = "0.1.0"
4requires-python = ">=3.12"
5dependencies = [
6 "psycopg>=3.2.9",
7 "psycopg-binary>=3.2.9",
8 "psycopg-pool>=3.2.6",
9 "sdnotify>=0.3.2",
10 "systemd-socketserver>=1.0",
11]
12
13[project.scripts]
14internal-policy-server = "internal_policy_server.__main__:main"
15
16[build-system]
17requires = ["hatchling"]
18build-backend = "hatchling.build"
diff --git a/hosts/surtr/email/internal-policy-server/uv.lock b/hosts/surtr/email/internal-policy-server/uv.lock
new file mode 100644
index 00000000..f7a4e729
--- /dev/null
+++ b/hosts/surtr/email/internal-policy-server/uv.lock
@@ -0,0 +1,119 @@
1version = 1
2revision = 2
3requires-python = ">=3.12"
4
5[[package]]
6name = "internal-policy-server"
7version = "0.1.0"
8source = { editable = "." }
9dependencies = [
10 { name = "psycopg" },
11 { name = "psycopg-binary" },
12 { name = "psycopg-pool" },
13 { name = "sdnotify" },
14 { name = "systemd-socketserver" },
15]
16
17[package.metadata]
18requires-dist = [
19 { name = "psycopg", specifier = ">=3.2.9" },
20 { name = "psycopg-binary", specifier = ">=3.2.9" },
21 { name = "psycopg-pool", specifier = ">=3.2.6" },
22 { name = "sdnotify", specifier = ">=0.3.2" },
23 { name = "systemd-socketserver", specifier = ">=1.0" },
24]
25
26[[package]]
27name = "psycopg"
28version = "3.2.9"
29source = { registry = "https://pypi.org/simple" }
30dependencies = [
31 { name = "typing-extensions", marker = "python_full_version < '3.13'" },
32 { name = "tzdata", marker = "sys_platform == 'win32'" },
33]
34sdist = { url = "https://files.pythonhosted.org/packages/27/4a/93a6ab570a8d1a4ad171a1f4256e205ce48d828781312c0bbaff36380ecb/psycopg-3.2.9.tar.gz", hash = "sha256:2fbb46fcd17bc81f993f28c47f1ebea38d66ae97cc2dbc3cad73b37cefbff700", size = 158122, upload-time = "2025-05-13T16:11:15.533Z" }
35wheels = [
36 { url = "https://files.pythonhosted.org/packages/44/b0/a73c195a56eb6b92e937a5ca58521a5c3346fb233345adc80fd3e2f542e2/psycopg-3.2.9-py3-none-any.whl", hash = "sha256:01a8dadccdaac2123c916208c96e06631641c0566b22005493f09663c7a8d3b6", size = 202705, upload-time = "2025-05-13T16:06:26.584Z" },
37]
38
39[[package]]
40name = "psycopg-binary"
41version = "3.2.9"
42source = { registry = "https://pypi.org/simple" }
43wheels = [
44 { url = "https://files.pythonhosted.org/packages/29/6f/ec9957e37a606cd7564412e03f41f1b3c3637a5be018d0849914cb06e674/psycopg_binary-3.2.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:be7d650a434921a6b1ebe3fff324dbc2364393eb29d7672e638ce3e21076974e", size = 4022205, upload-time = "2025-05-13T16:07:48.195Z" },
45 { url = "https://files.pythonhosted.org/packages/6b/ba/497b8bea72b20a862ac95a94386967b745a472d9ddc88bc3f32d5d5f0d43/psycopg_binary-3.2.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6a76b4722a529390683c0304501f238b365a46b1e5fb6b7249dbc0ad6fea51a0", size = 4083795, upload-time = "2025-05-13T16:07:50.917Z" },
46 { url = "https://files.pythonhosted.org/packages/42/07/af9503e8e8bdad3911fd88e10e6a29240f9feaa99f57d6fac4a18b16f5a0/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96a551e4683f1c307cfc3d9a05fec62c00a7264f320c9962a67a543e3ce0d8ff", size = 4655043, upload-time = "2025-05-13T16:07:54.857Z" },
47 { url = "https://files.pythonhosted.org/packages/28/ed/aff8c9850df1648cc6a5cc7a381f11ee78d98a6b807edd4a5ae276ad60ad/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:61d0a6ceed8f08c75a395bc28cb648a81cf8dee75ba4650093ad1a24a51c8724", size = 4477972, upload-time = "2025-05-13T16:07:57.925Z" },
48 { url = "https://files.pythonhosted.org/packages/5c/bd/8e9d1b77ec1a632818fe2f457c3a65af83c68710c4c162d6866947d08cc5/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad280bbd409bf598683dda82232f5215cfc5f2b1bf0854e409b4d0c44a113b1d", size = 4737516, upload-time = "2025-05-13T16:08:01.616Z" },
49 { url = "https://files.pythonhosted.org/packages/46/ec/222238f774cd5a0881f3f3b18fb86daceae89cc410f91ef6a9fb4556f236/psycopg_binary-3.2.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76eddaf7fef1d0994e3d536ad48aa75034663d3a07f6f7e3e601105ae73aeff6", size = 4436160, upload-time = "2025-05-13T16:08:04.278Z" },
50 { url = "https://files.pythonhosted.org/packages/37/78/af5af2a1b296eeca54ea7592cd19284739a844974c9747e516707e7b3b39/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:52e239cd66c4158e412318fbe028cd94b0ef21b0707f56dcb4bdc250ee58fd40", size = 3753518, upload-time = "2025-05-13T16:08:07.567Z" },
51 { url = "https://files.pythonhosted.org/packages/ec/ac/8a3ed39ea069402e9e6e6a2f79d81a71879708b31cc3454283314994b1ae/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:08bf9d5eabba160dd4f6ad247cf12f229cc19d2458511cab2eb9647f42fa6795", size = 3313598, upload-time = "2025-05-13T16:08:09.999Z" },
52 { url = "https://files.pythonhosted.org/packages/da/43/26549af068347c808fbfe5f07d2fa8cef747cfff7c695136172991d2378b/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1b2cf018168cad87580e67bdde38ff5e51511112f1ce6ce9a8336871f465c19a", size = 3407289, upload-time = "2025-05-13T16:08:12.66Z" },
53 { url = "https://files.pythonhosted.org/packages/67/55/ea8d227c77df8e8aec880ded398316735add8fda5eb4ff5cc96fac11e964/psycopg_binary-3.2.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:14f64d1ac6942ff089fc7e926440f7a5ced062e2ed0949d7d2d680dc5c00e2d4", size = 3472493, upload-time = "2025-05-13T16:08:15.672Z" },
54 { url = "https://files.pythonhosted.org/packages/3c/02/6ff2a5bc53c3cd653d281666728e29121149179c73fddefb1e437024c192/psycopg_binary-3.2.9-cp312-cp312-win_amd64.whl", hash = "sha256:7a838852e5afb6b4126f93eb409516a8c02a49b788f4df8b6469a40c2157fa21", size = 2927400, upload-time = "2025-05-13T16:08:18.652Z" },
55 { url = "https://files.pythonhosted.org/packages/28/0b/f61ff4e9f23396aca674ed4d5c9a5b7323738021d5d72d36d8b865b3deaf/psycopg_binary-3.2.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:98bbe35b5ad24a782c7bf267596638d78aa0e87abc7837bdac5b2a2ab954179e", size = 4017127, upload-time = "2025-05-13T16:08:21.391Z" },
56 { url = "https://files.pythonhosted.org/packages/bc/00/7e181fb1179fbfc24493738b61efd0453d4b70a0c4b12728e2b82db355fd/psycopg_binary-3.2.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:72691a1615ebb42da8b636c5ca9f2b71f266be9e172f66209a361c175b7842c5", size = 4080322, upload-time = "2025-05-13T16:08:24.049Z" },
57 { url = "https://files.pythonhosted.org/packages/58/fd/94fc267c1d1392c4211e54ccb943be96ea4032e761573cf1047951887494/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25ab464bfba8c401f5536d5aa95f0ca1dd8257b5202eede04019b4415f491351", size = 4655097, upload-time = "2025-05-13T16:08:27.376Z" },
58 { url = "https://files.pythonhosted.org/packages/41/17/31b3acf43de0b2ba83eac5878ff0dea5a608ca2a5c5dd48067999503a9de/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e8aeefebe752f46e3c4b769e53f1d4ad71208fe1150975ef7662c22cca80fab", size = 4482114, upload-time = "2025-05-13T16:08:30.781Z" },
59 { url = "https://files.pythonhosted.org/packages/85/78/b4d75e5fd5a85e17f2beb977abbba3389d11a4536b116205846b0e1cf744/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7e4e4dd177a8665c9ce86bc9caae2ab3aa9360b7ce7ec01827ea1baea9ff748", size = 4737693, upload-time = "2025-05-13T16:08:34.625Z" },
60 { url = "https://files.pythonhosted.org/packages/3b/95/7325a8550e3388b00b5e54f4ced5e7346b531eb4573bf054c3dbbfdc14fe/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fc2915949e5c1ea27a851f7a472a7da7d0a40d679f0a31e42f1022f3c562e87", size = 4437423, upload-time = "2025-05-13T16:08:37.444Z" },
61 { url = "https://files.pythonhosted.org/packages/1a/db/cef77d08e59910d483df4ee6da8af51c03bb597f500f1fe818f0f3b925d3/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a1fa38a4687b14f517f049477178093c39c2a10fdcced21116f47c017516498f", size = 3758667, upload-time = "2025-05-13T16:08:40.116Z" },
62 { url = "https://files.pythonhosted.org/packages/95/3e/252fcbffb47189aa84d723b54682e1bb6d05c8875fa50ce1ada914ae6e28/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5be8292d07a3ab828dc95b5ee6b69ca0a5b2e579a577b39671f4f5b47116dfd2", size = 3320576, upload-time = "2025-05-13T16:08:43.243Z" },
63 { url = "https://files.pythonhosted.org/packages/1c/cd/9b5583936515d085a1bec32b45289ceb53b80d9ce1cea0fef4c782dc41a7/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:778588ca9897b6c6bab39b0d3034efff4c5438f5e3bd52fda3914175498202f9", size = 3411439, upload-time = "2025-05-13T16:08:47.321Z" },
64 { url = "https://files.pythonhosted.org/packages/45/6b/6f1164ea1634c87956cdb6db759e0b8c5827f989ee3cdff0f5c70e8331f2/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f0d5b3af045a187aedbd7ed5fc513bd933a97aaff78e61c3745b330792c4345b", size = 3477477, upload-time = "2025-05-13T16:08:51.166Z" },
65 { url = "https://files.pythonhosted.org/packages/7b/1d/bf54cfec79377929da600c16114f0da77a5f1670f45e0c3af9fcd36879bc/psycopg_binary-3.2.9-cp313-cp313-win_amd64.whl", hash = "sha256:2290bc146a1b6a9730350f695e8b670e1d1feb8446597bed0bbe7c3c30e0abcb", size = 2928009, upload-time = "2025-05-13T16:08:53.67Z" },
66]
67
68[[package]]
69name = "psycopg-pool"
70version = "3.2.6"
71source = { registry = "https://pypi.org/simple" }
72dependencies = [
73 { name = "typing-extensions" },
74]
75sdist = { url = "https://files.pythonhosted.org/packages/cf/13/1e7850bb2c69a63267c3dbf37387d3f71a00fd0e2fa55c5db14d64ba1af4/psycopg_pool-3.2.6.tar.gz", hash = "sha256:0f92a7817719517212fbfe2fd58b8c35c1850cdd2a80d36b581ba2085d9148e5", size = 29770, upload-time = "2025-02-26T12:03:47.129Z" }
76wheels = [
77 { url = "https://files.pythonhosted.org/packages/47/fd/4feb52a55c1a4bd748f2acaed1903ab54a723c47f6d0242780f4d97104d4/psycopg_pool-3.2.6-py3-none-any.whl", hash = "sha256:5887318a9f6af906d041a0b1dc1c60f8f0dda8340c2572b74e10907b51ed5da7", size = 38252, upload-time = "2025-02-26T12:03:45.073Z" },
78]
79
80[[package]]
81name = "sdnotify"
82version = "0.3.2"
83source = { registry = "https://pypi.org/simple" }
84sdist = { url = "https://files.pythonhosted.org/packages/ce/d8/9fdc36b2a912bf78106de4b3f0de3891ff8f369e7a6f80be842b8b0b6bd5/sdnotify-0.3.2.tar.gz", hash = "sha256:73977fc746b36cc41184dd43c3fe81323e7b8b06c2bb0826c4f59a20c56bb9f1", size = 2459, upload-time = "2017-08-02T20:03:44.395Z" }
85
86[[package]]
87name = "systemd-python"
88version = "235"
89source = { registry = "https://pypi.org/simple" }
90sdist = { url = "https://files.pythonhosted.org/packages/10/9e/ab4458e00367223bda2dd7ccf0849a72235ee3e29b36dce732685d9b7ad9/systemd-python-235.tar.gz", hash = "sha256:4e57f39797fd5d9e2d22b8806a252d7c0106c936039d1e71c8c6b8008e695c0a", size = 61677, upload-time = "2023-02-11T13:42:16.588Z" }
91
92[[package]]
93name = "systemd-socketserver"
94version = "1.0"
95source = { registry = "https://pypi.org/simple" }
96dependencies = [
97 { name = "systemd-python" },
98]
99wheels = [
100 { url = "https://files.pythonhosted.org/packages/d8/4f/b28b7f08880120a26669b080ca74487c8c67e8b54dcb0467a8f0c9f38ed6/systemd_socketserver-1.0-py3-none-any.whl", hash = "sha256:987a8bfbf28d959e7c2966c742ad7bad482f05e121077defcf95bb38267db9a8", size = 3248, upload-time = "2020-04-26T05:26:40.661Z" },
101]
102
103[[package]]
104name = "typing-extensions"
105version = "4.13.2"
106source = { registry = "https://pypi.org/simple" }
107sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" }
108wheels = [
109 { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" },
110]
111
112[[package]]
113name = "tzdata"
114version = "2025.2"
115source = { registry = "https://pypi.org/simple" }
116sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" }
117wheels = [
118 { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" },
119]
diff --git a/hosts/surtr/hledger.nix b/hosts/surtr/hledger.nix
new file mode 100644
index 00000000..e44933c3
--- /dev/null
+++ b/hosts/surtr/hledger.nix
@@ -0,0 +1,66 @@
1{ config, ... }:
2
3{
4 config = {
5 security.acme.rfc2136Domains = {
6 "hledger.yggdrasil.li" = {
7 restartUnits = ["nginx.service"];
8 };
9 };
10
11 services.nginx = {
12 upstreams."hledger" = {
13 servers = {
14 "[2a03:4000:52:ada:4:1::]:5000" = {};
15 };
16 extraConfig = ''
17 keepalive 8;
18 '';
19 };
20 virtualHosts = {
21 "hledger.yggdrasil.li" = {
22 kTLS = true;
23 http3 = true;
24 forceSSL = true;
25 sslCertificate = "/run/credentials/nginx.service/hledger.yggdrasil.li.pem";
26 sslCertificateKey = "/run/credentials/nginx.service/hledger.yggdrasil.li.key.pem";
27 sslTrustedCertificate = "/run/credentials/nginx.service/hledger.yggdrasil.li.chain.pem";
28 extraConfig = ''
29 charset utf-8;
30 '';
31
32 locations = {
33 "/".extraConfig = ''
34 proxy_pass http://hledger;
35
36 proxy_http_version 1.1;
37 proxy_set_header Upgrade $http_upgrade;
38 proxy_set_header Connection "upgrade";
39
40 proxy_redirect off;
41 proxy_set_header Host $host;
42 proxy_set_header X-Real-IP $remote_addr;
43 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
44 proxy_set_header X-Forwarded-Host $server_name;
45 proxy_set_header X-Forwarded-Proto $scheme;
46
47 client_max_body_size 0;
48 proxy_request_buffering off;
49 proxy_buffering off;
50 '';
51 };
52 };
53 };
54 };
55
56 systemd.services.nginx = {
57 serviceConfig = {
58 LoadCredential = [
59 "hledger.yggdrasil.li.key.pem:${config.security.acme.certs."hledger.yggdrasil.li".directory}/key.pem"
60 "hledger.yggdrasil.li.pem:${config.security.acme.certs."hledger.yggdrasil.li".directory}/fullchain.pem"
61 "hledger.yggdrasil.li.chain.pem:${config.security.acme.certs."hledger.yggdrasil.li".directory}/chain.pem"
62 ];
63 };
64 };
65 };
66}
diff --git a/hosts/surtr/kimai.nix b/hosts/surtr/kimai.nix
new file mode 100644
index 00000000..454b3d80
--- /dev/null
+++ b/hosts/surtr/kimai.nix
@@ -0,0 +1,68 @@
1{ config, ... }:
2
3{
4 config = {
5 security.acme.rfc2136Domains = {
6 "kimai.yggdrasil.li" = {
7 restartUnits = ["nginx.service"];
8 };
9 };
10
11 services.nginx = {
12 upstreams."kimai" = {
13 servers = {
14 "[2a03:4000:52:ada:6::2]:80" = {};
15 };
16 extraConfig = ''
17 keepalive 8;
18 '';
19 };
20 virtualHosts = {
21 "kimai.yggdrasil.li" = {
22 kTLS = true;
23 http3 = true;
24 forceSSL = true;
25 sslCertificate = "/run/credentials/nginx.service/kimai.yggdrasil.li.pem";
26 sslCertificateKey = "/run/credentials/nginx.service/kimai.yggdrasil.li.key.pem";
27 sslTrustedCertificate = "/run/credentials/nginx.service/kimai.yggdrasil.li.chain.pem";
28 extraConfig = ''
29 charset utf-8;
30 '';
31
32 locations = {
33 "/".extraConfig = ''
34 proxy_pass http://kimai;
35
36 proxy_http_version 1.1;
37 proxy_set_header Upgrade $http_upgrade;
38 proxy_set_header Connection "upgrade";
39
40 proxy_redirect off;
41 proxy_set_header Host $host;
42 proxy_set_header X-Real-IP $remote_addr;
43 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
44 proxy_set_header X-Forwarded-Host $server_name;
45 proxy_set_header X-Forwarded-Proto $scheme;
46
47 client_max_body_size 0;
48 proxy_request_buffering off;
49 proxy_buffering off;
50
51 proxy_read_timeout 300;
52 '';
53 };
54 };
55 };
56 };
57
58 systemd.services.nginx = {
59 serviceConfig = {
60 LoadCredential = [
61 "kimai.yggdrasil.li.key.pem:${config.security.acme.certs."kimai.yggdrasil.li".directory}/key.pem"
62 "kimai.yggdrasil.li.pem:${config.security.acme.certs."kimai.yggdrasil.li".directory}/fullchain.pem"
63 "kimai.yggdrasil.li.chain.pem:${config.security.acme.certs."kimai.yggdrasil.li".directory}/chain.pem"
64 ];
65 };
66 };
67 };
68}
diff --git a/hosts/surtr/paperless.nix b/hosts/surtr/paperless.nix
new file mode 100644
index 00000000..7bc4397c
--- /dev/null
+++ b/hosts/surtr/paperless.nix
@@ -0,0 +1,66 @@
1{ config, ... }:
2
3{
4 config = {
5 security.acme.rfc2136Domains = {
6 "paperless.yggdrasil.li" = {
7 restartUnits = ["nginx.service"];
8 };
9 };
10
11 services.nginx = {
12 upstreams."paperless" = {
13 servers = {
14 "[2a03:4000:52:ada:4:1::]:28981" = {};
15 };
16 extraConfig = ''
17 keepalive 8;
18 '';
19 };
20 virtualHosts = {
21 "paperless.yggdrasil.li" = {
22 kTLS = true;
23 http3 = true;
24 forceSSL = true;
25 sslCertificate = "/run/credentials/nginx.service/paperless.yggdrasil.li.pem";
26 sslCertificateKey = "/run/credentials/nginx.service/paperless.yggdrasil.li.key.pem";
27 sslTrustedCertificate = "/run/credentials/nginx.service/paperless.yggdrasil.li.chain.pem";
28 extraConfig = ''
29 charset utf-8;
30 '';
31
32 locations = {
33 "/".extraConfig = ''
34 proxy_pass http://paperless;
35
36 proxy_http_version 1.1;
37 proxy_set_header Upgrade $http_upgrade;
38 proxy_set_header Connection "upgrade";
39
40 proxy_redirect off;
41 proxy_set_header Host $host;
42 proxy_set_header X-Real-IP $remote_addr;
43 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
44 proxy_set_header X-Forwarded-Host $server_name;
45 proxy_set_header X-Forwarded-Proto $scheme;
46
47 client_max_body_size 0;
48 proxy_request_buffering off;
49 proxy_buffering off;
50 '';
51 };
52 };
53 };
54 };
55
56 systemd.services.nginx = {
57 serviceConfig = {
58 LoadCredential = [
59 "paperless.yggdrasil.li.key.pem:${config.security.acme.certs."paperless.yggdrasil.li".directory}/key.pem"
60 "paperless.yggdrasil.li.pem:${config.security.acme.certs."paperless.yggdrasil.li".directory}/fullchain.pem"
61 "paperless.yggdrasil.li.chain.pem:${config.security.acme.certs."paperless.yggdrasil.li".directory}/chain.pem"
62 ];
63 };
64 };
65 };
66}
diff --git a/hosts/surtr/postgresql/default.nix b/hosts/surtr/postgresql/default.nix
index 583e4443..3786ea7c 100644
--- a/hosts/surtr/postgresql/default.nix
+++ b/hosts/surtr/postgresql/default.nix
@@ -89,6 +89,10 @@ in {
89 "d /var/spool/pgbackrest 0750 postgres postgres - -" 89 "d /var/spool/pgbackrest 0750 postgres postgres - -"
90 ]; 90 ];
91 91
92 systemd.services.postgresql.serviceConfig = {
93 ReadWritePaths = [ "/var/spool/pgbackrest" "/var/lib/pgbackrest/archive/surtr" ];
94 };
95
92 systemd.services.migrate-postgresql = { 96 systemd.services.migrate-postgresql = {
93 after = [ "postgresql.service" ]; 97 after = [ "postgresql.service" ];
94 bindsTo = [ "postgresql.service" ]; 98 bindsTo = [ "postgresql.service" ];
@@ -276,6 +280,64 @@ in {
276 CREATE VIEW imap_user ("user", "password", quota_rule) AS SELECT mailbox.mailbox AS "user", "password", quota_rule FROM mailbox_quota_rule INNER JOIN mailbox ON mailbox_quota_rule.mailbox = mailbox.mailbox; 280 CREATE VIEW imap_user ("user", "password", quota_rule) AS SELECT mailbox.mailbox AS "user", "password", quota_rule FROM mailbox_quota_rule INNER JOIN mailbox ON mailbox_quota_rule.mailbox = mailbox.mailbox;
277 281
278 COMMIT; 282 COMMIT;
283
284 BEGIN;
285 SELECT _v.register_patch('013-internal', ARRAY['000-base'], null);
286
287 ALTER TABLE mailbox_mapping ADD COLUMN internal bool NOT NULL DEFAULT false;
288 CREATE TABLE mailbox_mapping_access (
289 id uuid PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(),
290 mailbox_mapping uuid REFERENCES mailbox_mapping(id),
291 mailbox uuid REFERENCES mailbox(id)
292 );
293 CREATE USER "postfix-internal-policy";
294 GRANT CONNECT ON DATABASE "email" TO "postfix-internal-policy";
295 ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO "postfix-internal-policy";
296 GRANT SELECT ON ALL TABLES IN SCHEMA public TO "postfix-internal-policy";
297
298 COMMIT;
299
300 BEGIN;
301 SELECT _v.register_patch('014-relay', ARRAY['000-base'], null);
302
303 CREATE TABLE relay_access (
304 id uuid PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(),
305 mailbox uuid REFERENCES mailbox(id),
306 domain citext NOT NULL CONSTRAINT domain_non_empty CHECK (domain <> ''')
307 );
308
309 COMMIT;
310
311 BEGIN;
312 SELECT _v.register_patch('015-relay-unique', ARRAY['000-base', '014-relay'], null);
313
314 CREATE UNIQUE INDEX relay_unique ON relay_access (mailbox, domain);
315
316 COMMIT;
317
318 BEGIN;
319 SELECT _v.register_patch('015-sender_bcc', null, null);
320
321 CREATE TABLE sender_bcc_maps (
322 id uuid PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(),
323 key text NOT NULL CONSTRAINT key_not_empty CHECK (key <> '''),
324 value text NOT NULL CONSTRAINT value_not_empty CHECK (value <> '''),
325 CONSTRAINT key_unique UNIQUE (key)
326 );
327
328 COMMIT;
329
330 BEGIN;
331 SELECT _v.register_patch('016-recipient_bcc', null, null);
332
333 CREATE TABLE recipient_bcc_maps (
334 id uuid PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(),
335 key text NOT NULL CONSTRAINT key_not_empty CHECK (key <> '''),
336 value text NOT NULL CONSTRAINT value_not_empty CHECK (value <> '''),
337 CONSTRAINT recipient_bcc_maps_key_unique UNIQUE (key)
338 );
339
340 COMMIT;
279 ''} 341 ''}
280 342
281 psql etebase postgres -eXf ${pkgs.writeText "etebase.sql" '' 343 psql etebase postgres -eXf ${pkgs.writeText "etebase.sql" ''
diff --git a/hosts/surtr/tls/tsig_key.gup b/hosts/surtr/tls/tsig_key.gup
index 825479e5..46a3789e 100644
--- a/hosts/surtr/tls/tsig_key.gup
+++ b/hosts/surtr/tls/tsig_key.gup
@@ -3,4 +3,4 @@
3keyFile=../dns/keys/${2:t}_acme 3keyFile=../dns/keys/${2:t}_acme
4gup -u $keyFile 4gup -u $keyFile
5sops -d --input-type=binary --output-type=binary ${keyFile} | yq -r '.key[0].secret' > $1 5sops -d --input-type=binary --output-type=binary ${keyFile} | yq -r '.key[0].secret' > $1
6sops -p '7ED22F4AA7BB55728B643DC5471B7D88E4EF66F8,30D3453B8CD02FE2A3E7C78C0FB536FB87AE8F51' --input-type=binary -e -i $1 6sops --input-type=binary -e -i $1
diff --git a/hosts/surtr/tls/tsig_keys/audiobookshelf.yggdrasil.li b/hosts/surtr/tls/tsig_keys/audiobookshelf.yggdrasil.li
new file mode 100644
index 00000000..8dd610dd
--- /dev/null
+++ b/hosts/surtr/tls/tsig_keys/audiobookshelf.yggdrasil.li
@@ -0,0 +1,19 @@
1{
2 "data": "ENC[AES256_GCM,data:r9jhdTlbDnCMq1QLJutn76uz1Ml8MFs7fXYRSiVYh1gafcXXsUZBq5+qqoQI,iv:un/luttuKpCiMf53fa2SRY0ffttGiYwT8DuHCKEnnEI=,tag:SkNULZSulQmP99aB/Ec+Fw==,type:str]",
3 "sops": {
4 "age": [
5 {
6 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
7 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkZGJzaEsrSU4raHlTVDVB\nczRnWVlSTTRuNXU0T3F1RTkxKytXeVJRdGpFCk9WMzNBR1NaTTMzN3BGQ2JmTjVt\nRU4rSWxCYjJPYVRzLzR0OVRYQm45TUkKLS0tIDNyMnpPN2VKUFFadTkveXRYeWps\nYUNaTjRJLzdWUnREaUVIWkpFV0FTZ2MKJS0K49SdkLW4p67FlgboHy/OVvCiUA7g\nuv5b+yotkQmh5xJwr7CUvwRewqJh56mg1yhWmE8wzpgLZMIjRXcQCQ==\n-----END AGE ENCRYPTED FILE-----\n"
8 },
9 {
10 "recipient": "age19a7j77w267z04zls7m28a8hj4a0g5af6ltye2d5wypg33c3l89csd4r9zq",
11 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBQQ1h3M1lXTXVNd0d6cmtU\nU2JtUzFFblJudmEycnJONkkwME9wWm5jWVFzCnRYVEFWaVNvSW9GZ05TRWF4L2ho\nanltVytEU3ZOdHk1VHY5aGJDUkdDdmcKLS0tIEtzOFVkbmpjbWN5d0c1VEpxc1Rr\nSzJwclYxeC9TVWNaK2gwUmJSY0x1ZVUKTNivp5iS+1tzVMjMn17/ncvHcELhjQ/B\n0OVz4VpKM2wv6CjEcIMxmchqT8p8GFYVRrKUdqO2GEKOoe8ANtidWA==\n-----END AGE ENCRYPTED FILE-----\n"
12 }
13 ],
14 "lastmodified": "2025-05-09T17:07:16Z",
15 "mac": "ENC[AES256_GCM,data:SwS+8UQnPgHORobKLu+u2pNaMdKIvR+etUed8btbbne/IX/Wpxt0qyPYXNNGGRkN3KAxTHWjRRdrKU1bkuTU3ER1c94T935ExDESKJLVjzaEF5VSWCqLyUNCMsY2ANw84UES2swK4YI4zF1CP7rD8tKFFld78IWZoeQ7XNGDMRA=,iv:neLvamISgQ5+aqW1iRj9xJoXq1weNNyy7KCFG2+WRQE=,tag:66SDO61WnKU6DVElo9CImg==,type:str]",
16 "unencrypted_suffix": "_unencrypted",
17 "version": "3.10.2"
18 }
19}
diff --git a/hosts/surtr/tls/tsig_keys/hledger.yggdrasil.li b/hosts/surtr/tls/tsig_keys/hledger.yggdrasil.li
new file mode 100644
index 00000000..ab6cdd68
--- /dev/null
+++ b/hosts/surtr/tls/tsig_keys/hledger.yggdrasil.li
@@ -0,0 +1,24 @@
1{
2 "data": "ENC[AES256_GCM,data:Yd70QIj9DE6a5IN+Mf2M5p95vkRMHRg9BXaM686W7BRtthOw9m54/5FK6JWr,iv:cIOIKinkqFFPgTZdewWVY0h6kM5hGfVzuA4iYNhwK5c=,tag:Ds/oI+TOERbIdcGbI4WoEg==,type:str]",
3 "sops": {
4 "kms": null,
5 "gcp_kms": null,
6 "azure_kv": null,
7 "hc_vault": null,
8 "age": [
9 {
10 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
11 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1NnJ4SmsvSlh3ZlliSm9C\nNG45clh3NEZWZ05jaHhVK0xqTVRFL2wxN1ZrCk5hL2p2ZjhtcDBjTEZscXFTNkY5\nemVZSUUwV2V5cFBTdWo4RWxsM2xROVEKLS0tIDBFSFlkUVJ0ajJEUENlelVFKzVk\ndnJhMURMU2o3WVBKZGNVRXBiNytqUFEKHivcSTYy5D770C0h7RsmLBmkIG9+MDoV\ngJHvfkGzXPKwmDTMKdHbIk+ctI+u0/1jMn/K2Q9OFnOIYxP3gHiFag==\n-----END AGE ENCRYPTED FILE-----\n"
12 },
13 {
14 "recipient": "age19a7j77w267z04zls7m28a8hj4a0g5af6ltye2d5wypg33c3l89csd4r9zq",
15 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArYUNCQ1U2MXpQdmM4SHR0\nTnJWZlFGTUQ1NjVqZ3Z2MGt5NFpBVFp0b0YwCkdseitkbzI1RkZyL3V5Z1pNMG9R\nVnEzRUxrTDQrS3BiMXB1QUFPeUcxZUkKLS0tIGJFODkwNFY3c0tBLzFBNjFiYjJk\nZW82bTdia2F1NHpNSG1IYmhWb1ZCTHMK9ovFx3+x5PrV4y6+RH5XA5DK2wRPXlAt\ncxxpRZIlmnvhZXIeCYE9yhHFmz3uAn0Oib1RUDblca9FlnF9tyYD6g==\n-----END AGE ENCRYPTED FILE-----\n"
16 }
17 ],
18 "lastmodified": "2025-02-19T17:13:51Z",
19 "mac": "ENC[AES256_GCM,data:ReRuK9qdZV8AbMzA9Yur0AZW+1RF3aRnfBvsKJkQtXsFdkmJQ4QkRGtL27RmjFdvQ3kXBIyhib7hYA60AJ0amduYrSScY0dtz8AurjyE4f2BGQ9/QeKRBfKXHxLvj4/xWNvS4+PVdGKkKbqIs8isz9n77WQQ3lTHop2K/TjaTuQ=,iv:gUhDK9oeUHdpQ2Fp8mFDIgPFo2JjHE0jjooL7FmvmrE=,tag:2lkwSl3j3oqamdLbM9wbow==,type:str]",
20 "pgp": null,
21 "unencrypted_suffix": "_unencrypted",
22 "version": "3.9.4"
23 }
24} \ No newline at end of file
diff --git a/hosts/surtr/tls/tsig_keys/kimai.yggdrasil.li b/hosts/surtr/tls/tsig_keys/kimai.yggdrasil.li
new file mode 100644
index 00000000..b9199975
--- /dev/null
+++ b/hosts/surtr/tls/tsig_keys/kimai.yggdrasil.li
@@ -0,0 +1,19 @@
1{
2 "data": "ENC[AES256_GCM,data:ATcU3Ix7o5d/49rD5H8je1ozTjoghrloMh5DIZ5WE3oYauUAknpGfr9xq92V,iv:vy9YK5Ot7CCjMtgAGVeAUQuaSw4F5kmmZ0GJYV9kCdQ=,tag:F/MXTUM2AI1fGXa9Ewn8yQ==,type:str]",
3 "sops": {
4 "age": [
5 {
6 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
7 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBDMEF0cUdydERYVzJCa3pW\nTlo0NUFON0d5RGJFVnVTNVg3cjNEUERQMEdFClEvQW5odlNEd2F1VTFmMWQrL2RB\ncllFZVpIVVJrNTJsSGF4UEdZMnVmQzAKLS0tIFUrQkkzRVZiOFNiTnFCT1pEYVRM\nQm8wV1JkQ3RrR1dkL0FsNkhsY2kxa1kKGnAo/6oibgXexUU31THdLu6X+pRtrkjD\nZnXGPZ2xaESDVUVEYQPVpNrjt9brZGJBI1BasrkEwHAXMbJC236yYQ==\n-----END AGE ENCRYPTED FILE-----\n"
8 },
9 {
10 "recipient": "age19a7j77w267z04zls7m28a8hj4a0g5af6ltye2d5wypg33c3l89csd4r9zq",
11 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3MGs1Z2ZqK2pqWHdVYTJH\naTlncHdPa3Zld0JhQW5Ccmc1SStWSnlDR0JrCmpML2d4TGdldUdoZCtaWVpPZVl0\nVm4waWVBS1orRS90ZS96N0Y2M29LY0UKLS0tIEI1Z2VVbVVxRUpOZEN4NnBRRklC\nQXloelZCb04xbmduTlVuL005TlRGMHMKfLB6zA3sj3HgDBC7VGfGVB6I1zJpt0PV\nkCV2yADgvAA2pT9HPg9IWAEpTPysOBiuE2jPNtFvylZYwTDHoumFnQ==\n-----END AGE ENCRYPTED FILE-----\n"
12 }
13 ],
14 "lastmodified": "2025-05-24T09:42:23Z",
15 "mac": "ENC[AES256_GCM,data:0pk1LpWPmX9td/TwJFxwWp5pTDyW78UtHXMDah+V9Tmgi8hH7ONdysgjwpDwS/c4zGnMA3qtobEL286U3//CTXt2qVsiUGLsnngzs2E6yBg8oGMYlGrch4M355Fl5ZxYsc8QLA6qWcuZ4H3QW8PnoqdJixcHoYLoxG01dzh4Bc0=,iv:zchk4enI1D80BkJLji5RLm7OTk3GeF8nYHuwqBxCXIM=,tag:bgkknPMqkSidi6bDFfv6UQ==,type:str]",
16 "unencrypted_suffix": "_unencrypted",
17 "version": "3.10.2"
18 }
19}
diff --git a/hosts/surtr/tls/tsig_keys/paperless.yggdrasil.li b/hosts/surtr/tls/tsig_keys/paperless.yggdrasil.li
new file mode 100644
index 00000000..b1029931
--- /dev/null
+++ b/hosts/surtr/tls/tsig_keys/paperless.yggdrasil.li
@@ -0,0 +1,24 @@
1{
2 "data": "ENC[AES256_GCM,data:D9l0pklD2KDZ4/TXHtXg00MmCnjCVVBG0AK9j5OxxBCyYseCTckp2P/iPOng,iv:DjvuKWPr/jldfk0eZ5+jWHN0RurdruR4Md7AMAPzRQg=,tag:h0c4m3hpATzzb6a7DVmi9w==,type:str]",
3 "sops": {
4 "kms": null,
5 "gcp_kms": null,
6 "azure_kv": null,
7 "hc_vault": null,
8 "age": [
9 {
10 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
11 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3S05aS0ZEcVU2T3BIOG13\nSDJLTUp4OG9ZYVluK2gxbUJDSFRaQ0xnS0YwClFkKzByanJGOWFwbTlISndyU0Rx\nN1FJb3FVaUZOVDBsWEdHTVNGaGNtMVkKLS0tIDI1RmxaMlhVd1FPL1dNdlRGK0Nq\nMFpJZTNnWncrbkV5YS82ZnhGRld0UG8KIuf7bC7GVxaGeR7gwC7kGu/wtBppjq4H\nyDT05CYJf9/EE3K5aJpIOlxyqowRs2SINvIVkyd5ggYkkxCctmGXjQ==\n-----END AGE ENCRYPTED FILE-----\n"
12 },
13 {
14 "recipient": "age19a7j77w267z04zls7m28a8hj4a0g5af6ltye2d5wypg33c3l89csd4r9zq",
15 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUV3ZqTVJkNWdHeTlKK3Vl\nVTdrdzE2Z3YzVmZxelNFMDNMTUJneHNtNzFRCmQxTU84ekV4bFVLajU0ajB6ZzZK\neEpuQTcyS1o4MW9xTU5nMXVUR0gxTmcKLS0tIGVzZC8xYTc1VkU2RzA2NFQ1K2xz\nLzhPVjBUcytWNGRsdnFob3A4aWljelkKFMlmigcEVzelcEiv6WGya1dsIOJYr7YT\naBHgMttV7zzYHLqvIVJSCz+uw2FqDyqN46twmzFC0HSHeiKbvRrHVw==\n-----END AGE ENCRYPTED FILE-----\n"
16 }
17 ],
18 "lastmodified": "2025-02-13T19:23:42Z",
19 "mac": "ENC[AES256_GCM,data:0Fcgq0pOZtBBSiK8pUr/jadXMdtbZYFhUbSe+7DQpB8Fo2r8cEoT+Cpcy7tu+l9eXUiDk/tXTBJyMXaW4XWwS/Fe6Zcb95UYaYR1Y6OM9JVPYmwd6QSeC13MwzhYaCDlBiWWq69Zn8grEg7npWo/LS9LK7IEbN7EI8o7QYDI6cw=,iv:C+8ZmVTNWySQ+/6j+YirSwZzoMqXRlgstk47Efxmqps=,tag:Be/6Ve6M/Dcm/6QrbF+JTw==,type:str]",
20 "pgp": null,
21 "unencrypted_suffix": "_unencrypted",
22 "version": "3.9.4"
23 }
24} \ No newline at end of file
diff --git a/hosts/surtr/vpn/default.nix b/hosts/surtr/vpn/default.nix
index 1bdcf74e..92223144 100644
--- a/hosts/surtr/vpn/default.nix
+++ b/hosts/surtr/vpn/default.nix
@@ -1,4 +1,4 @@
1{ pkgs, config, lib, ... }: 1{ flake, pkgs, config, lib, ... }:
2 2
3with lib; 3with lib;
4 4
@@ -22,7 +22,11 @@ in {
22 "--load-credential=surtr.priv:/run/credentials/container@vpn.service/surtr.priv" 22 "--load-credential=surtr.priv:/run/credentials/container@vpn.service/surtr.priv"
23 "--network-ipvlan=ens3:upstream" 23 "--network-ipvlan=ens3:upstream"
24 ]; 24 ];
25 config = { 25 config = let hostConfig = config; in { config, pkgs, ... }: {
26 system.stateVersion = lib.mkIf hostConfig.containers."vpn".ephemeral config.system.nixos.release;
27 system.configurationRevision = mkIf (flake ? rev) flake.rev;
28 nixpkgs.pkgs = hostConfig.nixpkgs.pkgs;
29
26 boot.kernel.sysctl = { 30 boot.kernel.sysctl = {
27 "net.core.rmem_max" = 4194304; 31 "net.core.rmem_max" = 4194304;
28 "net.core.wmem_max" = 4194304; 32 "net.core.wmem_max" = 4194304;
diff --git a/hosts/surtr/vpn/geri.pub b/hosts/surtr/vpn/geri.pub
index ed5de2b2..2cd9b24e 100644
--- a/hosts/surtr/vpn/geri.pub
+++ b/hosts/surtr/vpn/geri.pub
@@ -1 +1 @@
sYuQSNZHzfegv8HRz71jnZm2nFLGeRnaGwVonhKUj2k= hhER05bvstOTGfiAG3IJsFkBNWCUZHokBXwaiC5d534=
diff --git a/hosts/surtr/zfs.nix b/hosts/surtr/zfs.nix
index 17c5cd32..3795956d 100644
--- a/hosts/surtr/zfs.nix
+++ b/hosts/surtr/zfs.nix
@@ -49,7 +49,7 @@
49 49
50 boot.postBootCommands = '' 50 boot.postBootCommands = ''
51 echo "=== STARTING ZPOOL IMPORT ===" 51 echo "=== STARTING ZPOOL IMPORT ==="
52 ${pkgs.zfs}/bin/zpool import -a -N -d /dev 52 ${pkgs.zfs}/bin/zpool import -a -f -N -d /dev
53 ${pkgs.zfs}/bin/zpool status 53 ${pkgs.zfs}/bin/zpool status
54 ${pkgs.zfs}/bin/zfs mount -a 54 ${pkgs.zfs}/bin/zfs mount -a
55 echo "=== ZPOOL IMPORT COMPLETE ===" 55 echo "=== ZPOOL IMPORT COMPLETE ==="
diff --git a/hosts/vidhar/audiobookshelf/abs-podcast-autoplaylist-gkleen.toml b/hosts/vidhar/audiobookshelf/abs-podcast-autoplaylist-gkleen.toml
new file mode 100644
index 00000000..a5319e38
--- /dev/null
+++ b/hosts/vidhar/audiobookshelf/abs-podcast-autoplaylist-gkleen.toml
@@ -0,0 +1,19 @@
1{
2 "data": "ENC[AES256_GCM,data:60OmHwuLC7RJVNNn8lsCFjIFrtDlmmT3yAm3DYn/K2b8OJB/lzKBhMUCyPpoI2lfMm6y47/DMwXI3ExH3QwfgGRf4i/Tcv7p6FCkjFgDc0RhAM7cXNSnh1gKTff8QYtPoNIzmycFCThNr7iZsPsf2/1npVaVHTnt9nTc+cmDLc+lELlvjSE00JOXch/if7KPwFww9K83XlrFmoRvwybfXR0unJqxK2XLvj+dQuKD4Bhyb88iSgu4dX1yw2uBSZBD16S4Io0DaZ+as5Yw4Kon7WMj3Jd5kz8ZxK+0NCy1CVJHOfJIwgYl0SVPp4DpbAPtJO4R/ciXyDQ/XGpoLtHjxnKXaJlJoSiA7FhuSEk+jB/peLHrYV1obdIRE5Dstly01S5cydKlfQ+A0TSjxFSWBYMEiD89sD09Br3iSJX5FejOoS8d2IQJ5faVzgQl4T5aBKsxCNNwmYrEe8m9HN7o2eer8nTKMln5IxZi3ZWhnjgJfrJ4QTXFndxCb78jo8HroN3+7VhoM136UZkqH1OMrIgAH/XSlW08G8m9MRamKsAWklq9aVflcEsPWTHmYW7rjAapQYf+jyK6BbfHcYmyKM82TFZ5iNB60Pth6EJgb2V8PZiChGvDzQvFYYOO3p9a/J8bVqsnPZBXXYcIBt42ZuRPvyyUTfM+75V1eYE9ZGFML+QlofwNCAg+/Rnl+RRy4z+8xQxd8Dn06geDpHsr4yND72FRUTKLbjxF5xfbzBRcZEXjGkyFdEAF7rB78I8xIqii+n6Yt8uEURmd4geI9KWXRQnwofTz9pklaAnRbER8zy/BJIiIYy8zecUHJn9v/DPnsnksfL6RRmG4tHaRBDbpAag0kVkCrpO/flK6dZOl/wvoVVVqT2O69a9/RpHLSV2f//ZS6L9s6vaYe4pXL0M6QymgA22sNHaws6XggJlTxVOFGRejMGYrKqVWtC+2UNbnel+/J0N1qj4luWfQaf9+1j+fq7vyLSzXYFCiyOLAznpqOhzKu6VWy2IbR0UnCoL5ZbhIba9e2MXM7Czy9Yee4xc=,iv:M0GbtFFl1XUeq+y9H+MiD+9z/ASB9hsd06KhpPzSwEo=,tag:vTLIIf+CeZN6DU25CSP8tw==,type:str]",
3 "sops": {
4 "age": [
5 {
6 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
7 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0aE1XNUNCM1Q5V0d3R2JG\nbjJZTmdvQ21JbmtyR0ZmODFMdVBGejRoam1vCjMzMGdTb3BReDVCa2JJU0JrSHFP\ndTdicU5TRjIrTWpteDMzeGtDT0xaelkKLS0tIFhaSlFrbzFDUjRZV0lGR0cydVdZ\nY2xma0VSVXlTM1JucFJUSys4dlRvdEUK9gQNQEdKDDf1ikWzd6uTlE50WsfO/EB0\nGH2Ono6oNWbKWTyl/wRO8NzXx0nudwqq66s0oBLIdTMQOpIBBNI0XQ==\n-----END AGE ENCRYPTED FILE-----\n"
8 },
9 {
10 "recipient": "age1qffdqvy9arld9zd5a5cylt0n98xhcns5shxhrhwjq5g4qa844ejselaa4l",
11 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBiRWFqSHNlY1IvMkkwaEto\ncHZHa2p1Y25SakFkS2JYMlRFcFhnZGY1dVRFCkxSWmxvcHZMampQKzdKRHI0ZVMx\nUTFtR0pHbzFaQ0xQUFA2ZERDSWpwS0UKLS0tIFBaSGczY3VWdy9TKzRDZWZ2SElY\nbVQ4dDNhQllmVmViWGs5c3V4TmNscjQKeugevQJFAN/8JrzeAm4hm2JsQGb26BCb\n3dKYnN1kJU7oVHr1aVfXwMpELNYt9poX6WTY2h9lsdHuRlqoFXAA5Q==\n-----END AGE ENCRYPTED FILE-----\n"
12 }
13 ],
14 "lastmodified": "2025-05-10T10:25:15Z",
15 "mac": "ENC[AES256_GCM,data:dhj7e+vF3uiR6I22PR5tdNdM8EyrWmGGTIqjj8H7IdNIsZBHzjeHlBDFOwN7z/JMO0BVwIi4DmhApg2BSPGsQZGDQZ28UTCC8TDtd1zmfGtSP8R8AFHADYdLK/desMtHg6BZTnLv5tpba34WWdflMNOQpwgWPZsIk/DkLaoXdvk=,iv:qkoAZngTz2sfWdxDs+h8Mb2IrkF8gqnQoR5iRoeKjbY=,tag:zXrkBJmPM4ItJxMnX8IDxQ==,type:str]",
16 "unencrypted_suffix": "_unencrypted",
17 "version": "3.10.2"
18 }
19}
diff --git a/hosts/vidhar/audiobookshelf/default.nix b/hosts/vidhar/audiobookshelf/default.nix
new file mode 100644
index 00000000..136bcaff
--- /dev/null
+++ b/hosts/vidhar/audiobookshelf/default.nix
@@ -0,0 +1,21 @@
1{ config, pkgs, lib, ... }:
2
3{
4 config = {
5 services.audiobookshelf = {
6 enable = true;
7 host = "2a03:4000:52:ada:4:1::";
8 port = 28982;
9 };
10
11 users.groups.audiobookshelf.members = [ "gkleen" ];
12
13 services.abs-podcast-autoplaylist = {
14 gkleen = {};
15 };
16 sops.secrets.${config.services.abs-podcast-autoplaylist.gkleen.configSecret} = {
17 format = "binary";
18 sopsFile = ./abs-podcast-autoplaylist-gkleen.toml;
19 };
20 };
21}
diff --git a/hosts/vidhar/default.nix b/hosts/vidhar/default.nix
index b0797d8a..547572c6 100644
--- a/hosts/vidhar/default.nix
+++ b/hosts/vidhar/default.nix
@@ -4,7 +4,7 @@ with lib;
4 4
5{ 5{
6 imports = with flake.nixosModules.systemProfiles; [ 6 imports = with flake.nixosModules.systemProfiles; [
7 ./zfs.nix ./network ./samba.nix ./dns ./prometheus ./borg ./pgbackrest ./postgresql.nix ./immich.nix 7 ./zfs.nix ./network ./samba.nix ./dns ./prometheus ./borg ./pgbackrest ./postgresql.nix ./immich.nix ./paperless ./hledger ./audiobookshelf ./kimai
8 tmpfs-root zfs 8 tmpfs-root zfs
9 initrd-all-crypto-modules default-locale openssh rebuild-machines 9 initrd-all-crypto-modules default-locale openssh rebuild-machines
10 build-server 10 build-server
@@ -136,7 +136,7 @@ with lib;
136 wantedBy = ["basic.target"]; 136 wantedBy = ["basic.target"];
137 serviceConfig = { 137 serviceConfig = {
138 ExecStart = pkgs.writeShellScript "limit-pstate-start" '' 138 ExecStart = pkgs.writeShellScript "limit-pstate-start" ''
139 echo 50 > /sys/devices/system/cpu/intel_pstate/max_perf_pct 139 echo 40 > /sys/devices/system/cpu/intel_pstate/max_perf_pct
140 ''; 140 '';
141 RemainAfterExit = true; 141 RemainAfterExit = true;
142 ExecStop = pkgs.writeShellScript "limit-pstate-stop" '' 142 ExecStop = pkgs.writeShellScript "limit-pstate-stop" ''
diff --git a/hosts/vidhar/hledger/default.nix b/hosts/vidhar/hledger/default.nix
new file mode 100644
index 00000000..ae080f66
--- /dev/null
+++ b/hosts/vidhar/hledger/default.nix
@@ -0,0 +1,83 @@
1{ config, lib, pkgs, ... }:
2{
3 config = {
4 services.hledger-web = {
5 enable = true;
6 allow = "view";
7 stateDir = "/var/lib/hledger";
8 journalFiles = lib.mkForce ["web.journal"];
9 baseUrl = "https://hledger.yggdrasil.li";
10 extraOptions = [
11 "--socket=/run/hledger-web/http.sock"
12 ];
13 };
14 users = {
15 users.hledger.uid = 982;
16 groups.hledger.gid = 979;
17 };
18 systemd.services.hledger-web = {
19 serviceConfig = {
20 UMask = "0002";
21 ReadOnlyPaths = [ config.services.hledger-web.stateDir ];
22 RuntimeDirectory = [ "hledger-web" ];
23 PrivateDevices = true;
24 StateDirectory = "hledger";
25 CapabilityBoundingSet = "";
26 AmbientCapabilities = "";
27 ProtectSystem = "strict";
28 ProtectKernelTunables = true;
29 ProtectKernelModules = true;
30 ProtectControlGroups = true;
31 ProtectClock = true;
32 ProtectHostname = true;
33 ProtectHome = "tmpfs";
34 ProtectKernelLogs = true;
35 ProtectProc = "invisible";
36 ProcSubset = "pid";
37 PrivateNetwork = false;
38 RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX";
39 SystemCallArchitectures = "native";
40 SystemCallFilter = [
41 "@system-service @resources"
42 "~@obsolete @privileged"
43 ];
44 RestrictSUIDSGID = true;
45 RemoveIPC = true;
46 NoNewPrivileges = true;
47 RestrictRealtime = true;
48 RestrictNamespaces = true;
49 LockPersonality = true;
50 PrivateUsers = true;
51 TemporaryFileSystem = [ "/var/lib/hledger/.cache:mode=0750,uid=${toString (config.users.users.hledger.uid)},gid=${toString (config.users.groups.hledger.gid)}" ];
52 };
53 };
54 services.nginx = {
55 upstreams.hledger = {
56 servers = { "unix:/run/hledger-web/http.sock" = {}; };
57 };
58 virtualHosts."hledger.yggdrasil.li" = {
59 listen = [
60 { addr = "[2a03:4000:52:ada:4:1::]"; port = 5000; }
61 ];
62 extraConfig = ''
63 set_real_ip_from 2a03:4000:52:ada:4::;
64 auth_basic "hledger";
65 auth_basic_user_file "/run/credentials/nginx.service/hledger_users";
66 '';
67 locations."/" = {
68 proxyPass = "http://hledger/";
69 proxyWebsockets = true;
70 };
71 };
72 };
73 systemd.services.nginx.serviceConfig = {
74 SupplementaryGroups = [ "hledger" ];
75 LoadCredential = [ "hledger_users:${config.sops.secrets."hledger_users".path}" ];
76 };
77 sops.secrets."hledger_users" = {
78 format = "binary";
79 sopsFile = ./htpasswd;
80 reloadUnits = [ "nginx.service" ];
81 };
82 };
83}
diff --git a/hosts/vidhar/hledger/htpasswd b/hosts/vidhar/hledger/htpasswd
new file mode 100644
index 00000000..016cb525
--- /dev/null
+++ b/hosts/vidhar/hledger/htpasswd
@@ -0,0 +1,24 @@
1{
2 "data": "ENC[AES256_GCM,data:9MNDIAc7ePYk3xQDorX2pU8ybJkJb33RKiJxc2DYauXFNQYxtGwCYhZwod7p7fPh3KqZxBNMRoZXr+/RnV+trsqjAcOOjnXTWLbX6nubq/xm+q0BxEjOPn7FvJF9XOblBeupldo+byGh2CMH9qQv5Fov,iv:3Tym+Mfr48OJet3qDFZPg0XjYr4sNQdNdiu0vUxmzbY=,tag:E0sxRY/jeMVlqH6uAYvD/Q==,type:str]",
3 "sops": {
4 "kms": null,
5 "gcp_kms": null,
6 "azure_kv": null,
7 "hc_vault": null,
8 "age": [
9 {
10 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
11 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3eFBsOEM2ZUNVT2V3LytC\nTUJvUDdKc0VzMyt2cDFKYU03djBjZVFpeVY4CjByMXhPVXRJVjhKQWZvQ2xuOTE3\ncXdJV1lZaHR3cVl0Z0hQaG00M2dGbjQKLS0tIEIzenVxb3cwM3pXTUl1YUZlSlk2\nbDc3VmE5NkEyZ2tRd01OUGZibmhtUlEKxdesIdvzm8s0SmXU5R+tSbmS5Dj24jrb\nEiMERYy1g8GyHR3d2/mU5iOIdsBegSZReUVzomaMT9L7/TmubgOP3g==\n-----END AGE ENCRYPTED FILE-----\n"
12 },
13 {
14 "recipient": "age1qffdqvy9arld9zd5a5cylt0n98xhcns5shxhrhwjq5g4qa844ejselaa4l",
15 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPa2RDZzR6cEFYTFA1QkND\nbndVeHVrMVJ0MWZvRmw5VXRhOHlRYllIRWxRCjU4dks4R25LS1RZMHFnbmpQRVZz\nNXhubkJvZFc2amRwMDVtQlE0NnBKNzQKLS0tIHRyeDUxTEFPMEMzWUVkZURzODdm\nSHdqbUpvNmFTS1QveFRpRHdnWHpHb28KnvdUkMkKGiBVHQD7Yv7n6WZjihCGJAR2\nMKl2WAn4g4jzgcXPwwIAIjUrMGSIdGpwCTUDcDnlKWAbRYO2B6P17A==\n-----END AGE ENCRYPTED FILE-----\n"
16 }
17 ],
18 "lastmodified": "2025-02-19T17:11:17Z",
19 "mac": "ENC[AES256_GCM,data:yBIEqHhr4igoMlRcgg2SigKfejqeuNmuleYolsLJo+QOaW4BHITJTvLxRV1JHPpcMVQkF//zx4ZfUUrb8tTN0znGu3Jnpd0JVagbfCVyEuT6d1SB/GzyUVvoQ2GlcA9us+5gjI4oEJTQCfVqnLDBWsw+jXdr3nEIWo6Mvbqo3lI=,iv:I6Swk4wyd+96+tJKRY/FHlS7ZShMDROcbl+l+ZLRxhM=,tag:P1uQvB4NLdkPEKRMI6lLxw==,type:str]",
20 "pgp": null,
21 "unencrypted_suffix": "_unencrypted",
22 "version": "3.9.4"
23 }
24} \ No newline at end of file
diff --git a/hosts/vidhar/kimai/default.nix b/hosts/vidhar/kimai/default.nix
new file mode 100644
index 00000000..0258697b
--- /dev/null
+++ b/hosts/vidhar/kimai/default.nix
@@ -0,0 +1,89 @@
1{ flake, config, ... }:
2
3{
4 config = {
5 boot.enableContainers = true;
6 boot.kernel.sysctl = {
7 "net.netfilter.nf_log_all_netns" = true;
8 };
9
10 containers."kimai" = {
11 autoStart = true;
12 ephemeral = true;
13 bindMounts = {
14 "/var/lib/kimai" = {
15 hostPath = "/var/lib/kimai/state";
16 isReadOnly = false;
17 };
18 "/var/lib/mysql" = {
19 hostPath = "/var/lib/kimai/mysql";
20 isReadOnly = false;
21 };
22 };
23 privateNetwork = true;
24 # forwardPorts = [
25 # { containerPort = 80;
26 # hostPort = 28983;
27 # }
28 # ];
29 hostAddress = "192.168.52.113";
30 localAddress = "192.168.52.114";
31 hostAddress6 = "2a03:4000:52:ada:6::1";
32 localAddress6 = "2a03:4000:52:ada:6::2";
33 config = let hostConfig = config; in { config, pkgs, lib, ... }: {
34 system.stateVersion = lib.mkIf hostConfig.containers."kimai".ephemeral config.system.nixos.release;
35 system.configurationRevision = lib.mkIf (flake ? rev) flake.rev;
36 nixpkgs.pkgs = hostConfig.nixpkgs.pkgs;
37
38 services.kimai.sites."kimai.yggdrasil.li" = {
39 database.socket = "/run/mysqld/mysqld.sock";
40 };
41
42 networking = {
43 useDHCP = false;
44 useNetworkd = true;
45 useHostResolvConf = false;
46 firewall.enable = false;
47 nftables = {
48 enable = true;
49 rulesetFile = ./ruleset.nft;
50 };
51 };
52
53 services.resolved.fallbackDns = [
54 "9.9.9.10#dns10.quad9.net"
55 "149.112.112.10#dns10.quad9.net"
56 "2620:fe::10#dns10.quad9.net"
57 "2620:fe::fe:10#dns10.quad9.net"
58 ];
59
60 systemd.network = {
61 networks.upstream = {
62 name = "eth0";
63 matchConfig = {
64 Name = "eth0";
65 };
66 linkConfig = {
67 RequiredForOnline = true;
68 };
69 networkConfig = {
70 Address = [ "192.168.52.114/32" "2a03:4000:52:ada:6::2/128" ];
71 LLMNR = false;
72 MulticastDNS = false;
73 };
74 routes = [
75 { Destination = "192.168.52.113/32"; }
76 { Destination = "2a03:4000:52:ada:6::1/128"; }
77 { Destination = "0.0.0.0/0";
78 Gateway = "192.168.52.113";
79 }
80 { Destination = "::/0";
81 Gateway = "2a03:4000:52:ada:6::1";
82 }
83 ];
84 };
85 };
86 };
87 };
88 };
89}
diff --git a/hosts/vidhar/kimai/ruleset.nft b/hosts/vidhar/kimai/ruleset.nft
new file mode 100644
index 00000000..ad4db6d5
--- /dev/null
+++ b/hosts/vidhar/kimai/ruleset.nft
@@ -0,0 +1,149 @@
1define icmp_protos = {ipv6-icmp, icmp, igmp}
2
3table arp filter {
4 limit lim_arp {
5 rate over 50 mbytes/second burst 50 mbytes
6 }
7
8 counter arp-rx {}
9 counter arp-tx {}
10
11 counter arp-ratelimit-rx {}
12 counter arp-ratelimit-tx {}
13
14 chain input {
15 type filter hook input priority filter
16 policy accept
17
18 limit name lim_arp counter name arp-ratelimit-rx drop
19
20 counter name arp-rx
21 }
22
23 chain output {
24 type filter hook output priority filter
25 policy accept
26
27 limit name lim_arp counter name arp-ratelimit-tx drop
28
29 counter name arp-tx
30 }
31}
32
33table inet filter {
34 limit lim_reject {
35 rate over 1000/second burst 1000 packets
36 }
37
38 limit lim_icmp {
39 rate over 50 mbytes/second burst 50 mbytes
40 }
41
42 counter invalid-fw {}
43 counter fw-lo {}
44
45 counter reject-ratelimit-fw {}
46 counter reject-fw {}
47 counter reject-tcp-fw {}
48 counter reject-icmp-fw {}
49
50 counter drop-fw {}
51
52 counter invalid-rx {}
53
54 counter rx-lo {}
55 counter invalid-local4-rx {}
56 counter invalid-local6-rx {}
57
58 counter icmp-ratelimit-rx {}
59 counter icmp-rx {}
60
61 counter kimai-rx {}
62
63 counter established-rx {}
64
65 counter reject-ratelimit-rx {}
66 counter reject-rx {}
67 counter reject-tcp-rx {}
68 counter reject-icmp-rx {}
69
70 counter drop-rx {}
71
72 counter tx-lo {}
73
74 counter icmp-ratelimit-tx {}
75 counter icmp-tx {}
76
77 counter kimai-tx {}
78
79 counter tx {}
80
81 chain forward {
82 type filter hook forward priority filter
83 policy drop
84
85
86 ct state invalid log level debug prefix "kimai: drop invalid forward: " counter name invalid-fw drop
87
88
89 iifname lo counter name fw-lo accept
90
91
92 limit name lim_reject log level debug prefix "kimai: drop forward: " counter name reject-ratelimit-fw drop
93 log level debug prefix "kimai: reject forward: " counter name reject-fw
94 meta l4proto tcp ct state new counter name reject-tcp-fw reject with tcp reset
95 ct state new counter name reject-icmp-fw reject
96
97
98 counter name drop-fw
99 }
100
101 chain input {
102 type filter hook input priority filter
103 policy drop
104
105
106 ct state invalid log level debug prefix "kimai: drop invalid input: " counter name invalid-rx drop
107
108
109 iifname lo counter name rx-lo accept
110 iif != lo ip daddr 127.0.0.1/8 counter name invalid-local4-rx reject
111 iif != lo ip6 daddr ::1/128 counter name invalid-local6-rx reject
112
113
114 meta l4proto $icmp_protos limit name lim_icmp counter name icmp-ratelimit-rx drop
115 meta l4proto $icmp_protos counter name icmp-rx accept
116
117
118 tcp dport 80 counter name kimai-rx accept
119
120
121 ct state { established, related } counter name established-rx accept
122
123
124 limit name lim_reject log level debug prefix "kimai: drop input: " counter name reject-ratelimit-rx drop
125 log level debug prefix "kimai: reject input: " counter name reject-rx
126 meta l4proto tcp ct state new counter name reject-tcp-rx reject with tcp reset
127 ct state new counter name reject-icmp-rx reject
128
129
130 counter name drop-rx
131 }
132
133 chain output {
134 type filter hook output priority filter
135 policy accept
136
137
138 oifname lo counter name tx-lo accept
139
140 meta l4proto $icmp_protos limit name lim_icmp counter name icmp-ratelimit-tx drop
141 meta l4proto $icmp_protos counter name icmp-tx accept
142
143
144 tcp sport 80 counter name kimai-tx
145
146
147 counter name tx
148 }
149}
diff --git a/hosts/vidhar/network/default.nix b/hosts/vidhar/network/default.nix
index 0643f0bb..92d755f3 100644
--- a/hosts/vidhar/network/default.nix
+++ b/hosts/vidhar/network/default.nix
@@ -103,7 +103,14 @@ with lib;
103 /srv/nfs/nix-store 10.141.0.0/24(ro,async,root_squash) 2a03:4000:52:ada:1::/80(ro,async,root_squash) 103 /srv/nfs/nix-store 10.141.0.0/24(ro,async,root_squash) 2a03:4000:52:ada:1::/80(ro,async,root_squash)
104 ''; 104 '';
105 }; 105 };
106 settings.nfsd.vers3 = false; 106 settings.nfsd = {
107 rdma = true;
108 vers3 = false;
109 vers4 = true;
110 "vers4.0" = false;
111 "vers4.1" = false;
112 "vers4.2" = true;
113 };
107 }; 114 };
108 115
109 fileSystems = { 116 fileSystems = {
diff --git a/hosts/vidhar/network/dhcp/default.nix b/hosts/vidhar/network/dhcp/default.nix
index 07a83351..11460393 100644
--- a/hosts/vidhar/network/dhcp/default.nix
+++ b/hosts/vidhar/network/dhcp/default.nix
@@ -1,8 +1,33 @@
1{ flake, config, pkgs, lib, ... }: 1{ flake, config, pkgs, lib, sources, ... }:
2 2
3with lib; 3with lib;
4 4
5{ 5let
6 nfsrootBaseUrl = "http://nfsroot.vidhar.yggdrasil";
7 tftpIp = "10.141.0.1";
8 nfsIp = tftpIp;
9 ipxe = pkgs.ipxe.override {
10 additionalTargets = {
11 "bin-i386-efi/ipxe.efi" = "i386-ipxe.efi";
12 };
13 additionalOptions = [
14 "NSLOOKUP_CMD"
15 "PING_CMD"
16 "CONSOLE_CMD"
17 ];
18 embedScript = pkgs.writeText "yggdrasil.ipxe" ''
19 #!ipxe
20
21 cpair --background 9 1
22 cpair --background 9 3
23 cpair --background 9 6
24
25 set user-class iPXE-yggdrasil
26
27 autoboot
28 '';
29 };
30in {
6 config = { 31 config = {
7 services.kea = { 32 services.kea = {
8 dhcp4 = { 33 dhcp4 = {
@@ -23,41 +48,67 @@ with lib;
23 }; 48 };
24 49
25 client-classes = [ 50 client-classes = [
26 { name = "eostre-ipxe"; 51 { name = "ipxe-eostre";
27 test = "hexstring(pkt4.mac, ':') == '00:d8:61:79:c5:40' and option[77].hex == 'iPXE'"; 52 test = "hexstring(pkt4.mac, ':') == '00:d8:61:79:c5:40' and option[77].hex == 'iPXE-yggdrasil'";
28 next-server = "10.141.0.1"; 53 next-server = tftpIp;
29 boot-file-name = "http://nfsroot.vidhar.yggdrasil/eostre/netboot.ipxe"; 54 boot-file-name = "${nfsrootBaseUrl}/eostre.menu.ipxe";
55 only-if-required = true;
56 }
57 { name = "ipxe-yggdrasil";
58 test = "option[77].hex == 'iPXE-yggdrasil'";
59 next-server = tftpIp;
60 boot-file-name = "${nfsrootBaseUrl}/installer-x86_64-linux.menu.ipxe";
61 only-if-required = true;
62 }
63
64 { name = "uefi-http";
65 test = "option[client-system].hex == 0x0010";
66 option-data = [
67 { name = "vendor-class-identifier"; data = "HTTPClient"; }
68 ];
69 boot-file-name = "${nfsrootBaseUrl}/ipxe.efi";
70 only-if-required = true;
71 }
72
73 { name = "ipxe-uefi-64";
74 test = "option[77].hex == 'iPXE' and (substring(option[60].hex,0,20) == 'PXEClient:Arch:00007' or substring(option[60].hex,0,20) == 'PXEClient:Arch:00008' or substring(option[60].hex,0,20) == 'PXEClient:Arch:00009')";
75 boot-file-name = "${nfsrootBaseUrl}/ipxe.efi";
76 only-if-required = true;
77 }
78 { name = "ipxe-uefi-32";
79 test = "option[77].hex == 'iPXE' and (substring(option[60].hex,0,20) == 'PXEClient:Arch:00002' or substring(option[60].hex,0,20) == 'PXEClient:Arch:00006')";
80 boot-file-name = "${nfsrootBaseUrl}/i386-ipxe.efi";
30 only-if-required = true; 81 only-if-required = true;
31 } 82 }
32 { name = "ipxe"; 83 { name = "ipxe-legacy";
33 test = "option[77].hex == 'iPXE'"; 84 test = "option[77].hex == 'iPXE' and substring(option[60].hex,0,20) == 'PXEClient:Arch:00000'";
34 next-server = "10.141.0.1"; 85 boot-file-name = "${nfsrootBaseUrl}/ipxe.lkrn";
35 boot-file-name = "http://nfsroot.vidhar.yggdrasil/installer-x86_64-linux/netboot.ipxe";
36 only-if-required = true; 86 only-if-required = true;
37 } 87 }
88
38 { name = "uefi-64"; 89 { name = "uefi-64";
39 test = "substring(option[60].hex,0,20) == 'PXEClient:Arch:00007' or substring(option[60].hex,0,20) == 'PXEClient:Arch:00008' or substring(option[60].hex,0,20) == 'PXEClient:Arch:00009'"; 90 test = "substring(option[60].hex,0,20) == 'PXEClient:Arch:00007' or substring(option[60].hex,0,20) == 'PXEClient:Arch:00008' or substring(option[60].hex,0,20) == 'PXEClient:Arch:00009'";
40 only-if-required = true;
41 option-data = [ 91 option-data = [
42 { name = "tftp-server-name"; data = "10.141.0.1"; } 92 { name = "tftp-server-name"; data = tftpIp; }
43 ]; 93 ];
44 boot-file-name = "ipxe.efi"; 94 boot-file-name = "ipxe.efi";
95 only-if-required = true;
45 } 96 }
46 { name = "uefi-32"; 97 { name = "uefi-32";
47 test = "substring(option[60].hex,0,20) == 'PXEClient:Arch:00002' or substring(option[60].hex,0,20) == 'PXEClient:Arch:00006'"; 98 test = "substring(option[60].hex,0,20) == 'PXEClient:Arch:00002' or substring(option[60].hex,0,20) == 'PXEClient:Arch:00006'";
48 only-if-required = true;
49 option-data = [ 99 option-data = [
50 { name = "tftp-server-name"; data = "10.141.0.1"; } 100 { name = "tftp-server-name"; data = tftpIp; }
51 ]; 101 ];
52 boot-file-name = "i386-ipxe.efi"; 102 boot-file-name = "i386-ipxe.efi";
103 only-if-required = true;
53 } 104 }
54 { name = "legacy"; 105 { name = "legacy";
55 test = "substring(option[60].hex,0,20) == 'PXEClient:Arch:00000'"; 106 test = "substring(option[60].hex,0,20) == 'PXEClient:Arch:00000'";
56 only-if-required = true;
57 option-data = [ 107 option-data = [
58 { name = "tftp-server-name"; data = "10.141.0.1"; } 108 { name = "tftp-server-name"; data = tftpIp; }
59 ]; 109 ];
60 boot-file-name = "undionly.kpxe"; 110 boot-file-name = "ipxe.lkrn";
111 only-if-required = true;
61 } 112 }
62 ]; 113 ];
63 114
@@ -252,34 +303,78 @@ with lib;
252 name = "nfsroot.vidhar.yggdrasil"; 303 name = "nfsroot.vidhar.yggdrasil";
253 paths = 304 paths =
254 (map (system: 305 (map (system:
255 let 306 pkgs.symlinkJoin {
256 installerBuild = (flake.nixosConfigurations.${"installer-${system}-nfsroot"}.extendModules { 307 name = "installer-${system}";
257 modules = [ 308 paths = [
258 ({ ... }: { 309 (builtins.addErrorContext "while evaluating installer-${system}-nfsroot" (let
259 config.nfsroot.storeDevice = "10.141.0.1:nix-store"; 310 installerBuild' = (flake.nixosConfigurations.${"installer-${system}-nfsroot"}.extendModules {
260 config.nfsroot.registrationUrl = "http://nfsroot.vidhar.yggdrasil/installer-${system}/registration"; 311 modules = [
261 }) 312 ({ ... }: {
262 ]; 313 config.nfsroot.storeDevice = "${nfsIp}:nix-store";
263 }).config.system.build; 314 config.nfsroot.registrationUrl = "${nfsrootBaseUrl}/installer-${system}/registration";
264 in builtins.toPath (pkgs.runCommandLocal "install-${system}" {} '' 315 config.system.nixos.label = "installer-${system}";
265 mkdir -p $out/installer-${system} 316 })
266 install -m 0444 -t $out/installer-${system} \ 317 ];
267 ${installerBuild.initialRamdisk}/initrd \ 318 });
268 ${installerBuild.kernel}/bzImage \ 319 installerBuild = installerBuild'.config.system.build;
269 ${installerBuild.netbootIpxeScript}/netboot.ipxe \ 320 in builtins.toPath (pkgs.runCommandLocal "installer-${system}" {} ''
270 ${pkgs.closureInfo { rootPaths = installerBuild.storeContents; }}/registration 321 mkdir -p $out/installer-${system}
271 '') 322 install -m 0444 -t $out/installer-${system} \
272 ) ["x86_64-linux"] 323 ${installerBuild.initialRamdisk}/initrd \
324 ${installerBuild.kernel}/bzImage \
325 ${installerBuild.netbootIpxeScript}/netboot.ipxe \
326 ${pkgs.closureInfo { rootPaths = installerBuild.storeContents; }}/registration
327 install -m 0444 ${pkgs.writeText "installer-${system}.menu.ipxe" ''
328 #!ipxe
329
330 :start
331 menu iPXE boot menu for installer-${system}
332 item installer ${with installerBuild'; "${config.system.nixos.distroName} ${config.system.nixos.codeName} ${config.system.nixos.label} (Linux ${config.boot.kernelPackages.kernel.modDirVersion})"}
333 item memtest memtest86plus
334 item netboot netboot.xyz
335 item shell iPXE shell
336 choose --timeout 0 --default installer selected || goto shell
337 goto ''${selected}
338
339 :shell
340 shell
341 goto start
342
343 :installer
344 chain installer-${system}/netboot.ipxe
345 goto start
346
347 :netboot
348 iseq ''${platform} efi && chain --autofree netboot.xyz.efi || chain --autofree netboot.xyz.lkrn
349 goto start
350
351 :memtest
352 iseq ''${platform} efi && chain --autofree memtest.efi || chain --autofree memtest.bin
353 goto start
354 ''} $out/installer-${system}.menu.ipxe
355 '')))
356 ];
357 }) ["x86_64-linux"]
273 ) ++ [ 358 ) ++ [
274 (let 359 (pkgs.runCommandLocal "utils" {} ''
275 eostreBuild = (flake.nixosConfigurations.eostre.extendModules { 360 mkdir $out
361 install -m 0444 -t $out \
362 ${ipxe}/{ipxe.efi,i386-ipxe.efi,ipxe.lkrn} \
363 ${pkgs.memtest86plus}/{memtest.efi,memtest.bin}
364 install -m 0444 ${sources.netbootxyz-efi.src} $out/netboot.xyz.efi
365 install -m 0444 ${sources.netbootxyz-lkrn.src} $out/netboot.xyz.lkrn
366 '')
367 (builtins.addErrorContext "while evaluating eostre" (let
368 eostreBuild' = (flake.nixosConfigurations.eostre.extendModules {
276 modules = [ 369 modules = [
277 ({ ... }: { 370 ({ ... }: {
278 config.nfsroot.storeDevice = "10.141.0.1:nix-store"; 371 config.nfsroot.storeDevice = "${nfsIp}:nix-store";
279 config.nfsroot.registrationUrl = "http://nfsroot.vidhar.yggdrasil/eostre/registration"; 372 config.nfsroot.registrationUrl = "${nfsrootBaseUrl}/eostre/registration";
373 config.system.nixos.label = "eostre";
280 }) 374 })
281 ]; 375 ];
282 }).config.system.build; 376 });
377 eostreBuild = eostreBuild'.config.system.build;
283 in builtins.toPath (pkgs.runCommandLocal "eostre" {} '' 378 in builtins.toPath (pkgs.runCommandLocal "eostre" {} ''
284 mkdir -p $out/eostre 379 mkdir -p $out/eostre
285 install -m 0444 -t $out/eostre \ 380 install -m 0444 -t $out/eostre \
@@ -287,7 +382,39 @@ with lib;
287 ${eostreBuild.kernel}/bzImage \ 382 ${eostreBuild.kernel}/bzImage \
288 ${eostreBuild.netbootIpxeScript}/netboot.ipxe \ 383 ${eostreBuild.netbootIpxeScript}/netboot.ipxe \
289 ${pkgs.closureInfo { rootPaths = eostreBuild.storeContents; }}/registration 384 ${pkgs.closureInfo { rootPaths = eostreBuild.storeContents; }}/registration
290 '')) 385 install -m 0444 ${pkgs.writeText "eostre.menu.ipxe" ''
386 #!ipxe
387
388 set menu-timeout 5000
389
390 :start
391 menu iPXE boot menu for eostre
392 item eostre ${with eostreBuild'; "${config.system.nixos.distroName} ${config.system.nixos.codeName} ${config.system.nixos.label} (Linux ${config.boot.kernelPackages.kernel.modDirVersion})"}
393 item memtest memtest86plus
394 item netboot netboot.xyz
395 item shell iPXE shell
396 choose --timeout ''${menu-timeout} --default eostre selected || goto shell
397 set menu-timeout 0
398 goto ''${selected}
399
400 :shell
401 set menu-timeout 0
402 shell
403 goto start
404
405 :eostre
406 chain eostre/netboot.ipxe
407 goto start
408
409 :netboot
410 iseq ''${platform} efi && chain --autofree netboot.xyz.efi || chain --autofree netboot.xyz.lkrn
411 goto start
412
413 :memtest
414 iseq ''${platform} efi && chain --autofree memtest.efi || chain --autofree memtest.bin
415 goto start
416 ''} $out/eostre.menu.ipxe
417 '')))
291 ]; 418 ];
292 }; 419 };
293 }; 420 };
@@ -298,20 +425,12 @@ with lib;
298 after = [ "network.target" ]; 425 after = [ "network.target" ];
299 wantedBy = [ "multi-user.target" ]; 426 wantedBy = [ "multi-user.target" ];
300 serviceConfig.ExecStart = let 427 serviceConfig.ExecStart = let
301 ipxe = pkgs.ipxe.override {
302 additionalTargets = {
303 "bin-i386-efi/ipxe.efi" = "i386-ipxe.efi";
304 };
305 additionalOptions = [
306 "NSLOOKUP_CMD"
307 ];
308 };
309 tftpRoot = pkgs.runCommandLocal "netboot" {} '' 428 tftpRoot = pkgs.runCommandLocal "netboot" {} ''
310 mkdir -p $out 429 mkdir -p $out
311 install -m 0444 -t $out \ 430 install -m 0444 -t $out \
312 ${ipxe}/ipxe.efi ${ipxe}/i386-ipxe.efi ${ipxe}/undionly.kpxe 431 ${ipxe}/{ipxe.efi,i386-ipxe.efi,ipxe.lkrn}
313 ''; 432 '';
314 in "${pkgs.atftp}/sbin/atftpd --daemon --no-fork --bind-address=10.141.0.1 ${tftpRoot}"; 433 in "${pkgs.atftp}/sbin/atftpd --daemon --no-fork --bind-address=${tftpIp} ${tftpRoot}";
315 }; 434 };
316 }; 435 };
317} 436}
diff --git a/hosts/vidhar/network/ruleset.nft b/hosts/vidhar/network/ruleset.nft
index 10fd4c51..7897fb3d 100644
--- a/hosts/vidhar/network/ruleset.nft
+++ b/hosts/vidhar/network/ruleset.nft
@@ -60,6 +60,7 @@ table inet filter {
60 counter fw-lo {} 60 counter fw-lo {}
61 counter fw-lan {} 61 counter fw-lan {}
62 counter fw-gpon {} 62 counter fw-gpon {}
63 counter fw-kimai {}
63 64
64 counter fw-cups {} 65 counter fw-cups {}
65 66
@@ -92,6 +93,10 @@ table inet filter {
92 counter tftp-rx {} 93 counter tftp-rx {}
93 counter pgbackrest-rx {} 94 counter pgbackrest-rx {}
94 counter immich-rx {} 95 counter immich-rx {}
96 counter paperless-rx {}
97 counter hledger-rx {}
98 counter audiobookshelf-rx {}
99 counter kimai-rx {}
95 100
96 counter established-rx {} 101 counter established-rx {}
97 102
@@ -121,6 +126,10 @@ table inet filter {
121 counter tftp-tx {} 126 counter tftp-tx {}
122 counter pgbackrest-tx {} 127 counter pgbackrest-tx {}
123 counter immich-tx {} 128 counter immich-tx {}
129 counter paperless-tx {}
130 counter hledger-tx {}
131 counter audiobookshelf-tx {}
132 counter kimai-tx {}
124 133
125 counter tx {} 134 counter tx {}
126 135
@@ -144,8 +153,13 @@ table inet filter {
144 153
145 oifname { lan, gpon, bifrost } meta l4proto $icmp_protos jump forward_icmp_accept 154 oifname { lan, gpon, bifrost } meta l4proto $icmp_protos jump forward_icmp_accept
146 iifname lan oifname { gpon, bifrost } counter name fw-lan accept 155 iifname lan oifname { gpon, bifrost } counter name fw-lan accept
156 iifname ve-kimai oifname gpon counter name fw-kimai accept
147 157
148 iifname gpon oifname lan ct state { established, related } counter name fw-gpon accept 158 iifname gpon oifname lan ct state { established, related } counter name fw-gpon accept
159 iifname gpon oifname ve-kimai ct state { established, related } counter name fw-kimai accept
160
161 iifname bifrost oifname ve-kimai tcp dport 80 ip6 saddr $bifrost_surtr ip6 daddr 2a03:4000:52:ada:6::2 counter name kimai-rx accept
162 iifname ve-kimai oifname bifrost tcp sport 80 ip6 saddr 2a03:4000:52:ada:6::2 ip6 daddr $bifrost_surtr counter name kimai-tx accept
149 163
150 164
151 limit name lim_reject log level debug prefix "drop forward: " counter name reject-ratelimit-fw drop 165 limit name lim_reject log level debug prefix "drop forward: " counter name reject-ratelimit-fw drop
@@ -197,6 +211,9 @@ table inet filter {
197 tcp dport 8432 counter name pgbackrest-rx accept 211 tcp dport 8432 counter name pgbackrest-rx accept
198 212
199 iifname bifrost tcp dport 2283 ip6 saddr $bifrost_surtr counter name immich-rx accept 213 iifname bifrost tcp dport 2283 ip6 saddr $bifrost_surtr counter name immich-rx accept
214 iifname bifrost tcp dport 28981 ip6 saddr $bifrost_surtr counter name paperless-rx accept
215 iifname bifrost tcp dport 5000 ip6 saddr $bifrost_surtr counter name hledger-rx accept
216 iifname bifrost tcp dport 28982 ip6 saddr $bifrost_surtr counter name audiobookshelf-rx accept
200 217
201 ct state { established, related } counter name established-rx accept 218 ct state { established, related } counter name established-rx accept
202 219
@@ -246,6 +263,9 @@ table inet filter {
246 tcp sport 8432 counter name pgbackrest-tx accept 263 tcp sport 8432 counter name pgbackrest-tx accept
247 264
248 iifname bifrost tcp sport 2283 ip6 daddr $bifrost_surtr counter name immich-tx accept 265 iifname bifrost tcp sport 2283 ip6 daddr $bifrost_surtr counter name immich-tx accept
266 iifname bifrost tcp sport 28981 ip6 daddr $bifrost_surtr counter name paperless-tx accept
267 iifname bifrost tcp sport 5000 ip6 daddr $bifrost_surtr counter name hledger-tx accept
268 iifname bifrost tcp sport 28982 ip6 daddr $bifrost_surtr counter name audiobookshelf-tx accept
249 269
250 270
251 counter name tx 271 counter name tx
@@ -254,7 +274,7 @@ table inet filter {
254 274
255table inet nat { 275table inet nat {
256 counter gpon-nat {} 276 counter gpon-nat {}
257 # counter container-nat {} 277 counter kimai-nat {}
258 278
259 chain postrouting { 279 chain postrouting {
260 type nat hook postrouting priority srcnat 280 type nat hook postrouting priority srcnat
@@ -262,7 +282,7 @@ table inet nat {
262 282
263 283
264 meta nfproto ipv4 oifname gpon counter name gpon-nat masquerade 284 meta nfproto ipv4 oifname gpon counter name gpon-nat masquerade
265 # iifname ve-* oifname gpon counter name container-nat masquerade 285 iifname ve-kimai oifname gpon counter name kimai-nat masquerade
266 } 286 }
267} 287}
268 288
diff --git a/hosts/vidhar/paperless/default.nix b/hosts/vidhar/paperless/default.nix
new file mode 100644
index 00000000..dd02da38
--- /dev/null
+++ b/hosts/vidhar/paperless/default.nix
@@ -0,0 +1,25 @@
1{ config, ... }:
2
3{
4 config = {
5 services.paperless = {
6 enable = true;
7 address = "2a03:4000:52:ada:4:1::";
8 passwordFile = config.sops.secrets."paperless-rootpw".path;
9 settings = {
10 PAPERLESS_OCR_LANGUAGE = "deu+eng";
11 PAPERLESS_URL = "https://paperless.yggdrasil.li";
12 PAPERLESS_FILENAME_FORMAT = "{{ created_year }}/{{ document_type }}/{{ correspondent }}/{{ created }}_{{ doc_pk }}_{{ title }}";
13 PAPERLESS_FILENAME_FORMAT_REMOVE_NONE = "true";
14 PAPERLESS_TASK_WORKERS = "3";
15 PAPERLESS_THREADS_PER_WORKER = "4";
16 };
17 database.createLocally = true;
18 };
19
20 sops.secrets."paperless-rootpw" = {
21 format = "binary";
22 sopsFile = ./rootpw;
23 };
24 };
25}
diff --git a/hosts/vidhar/paperless/rootpw b/hosts/vidhar/paperless/rootpw
new file mode 100644
index 00000000..11f48fcb
--- /dev/null
+++ b/hosts/vidhar/paperless/rootpw
@@ -0,0 +1,24 @@
1{
2 "data": "ENC[AES256_GCM,data:Bsns3bLs7aA++eTf2Vh4g2iAXhmrMRTF,iv:zQ6hgXEvgHAloN6UMW54f2nYCvEhHPXQSBVSihHFiC0=,tag:uiGTEs07dpx12PcAjmbr9Q==,type:str]",
3 "sops": {
4 "kms": null,
5 "gcp_kms": null,
6 "azure_kv": null,
7 "hc_vault": null,
8 "age": [
9 {
10 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
11 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlVUJjdEdIZGd6UDJBRXlL\nODFyWDhHOU9oTEVCVlFiUXVXNm9XZmVuampVCkJ0YkFXTlZXVnRldmtlVkJaR3R2\nMFhpaHB5M3pLeDFkUkkzMUFydGNnOFEKLS0tIEJtNWc0V2JaaWYvQlp6TGxVdVZO\neVpzQzB5Um82TUZOeHBHeE50MGlqNWsKj1P54Fc+c5n35+Og9DwBWkvW947hgFsp\ni/G2QcaLHHJMTexTCZYsr1naSVa/cMBAbrZmtjz0HV4Q1kCJtvlrIg==\n-----END AGE ENCRYPTED FILE-----\n"
12 },
13 {
14 "recipient": "age1qffdqvy9arld9zd5a5cylt0n98xhcns5shxhrhwjq5g4qa844ejselaa4l",
15 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA1UG1QSWtXcFZoQVRBOC9D\nT2VnTW9pcTRCMForcHdZVld0c1NmNFZpWUNBCkRkMERKUVliYXRqb25saWxyb2JN\nbC9YL2ZQbytRM0ZjNmlQOTlTZTQrV2sKLS0tIFZyUWtRcXNqZUZxMGN5d0tHUng2\nVXNSdFEwMmtIVEdVRVlWeVU1YmJVSkUKRJa42k551QtiC6S0tmMv7eVN7GRqpXWz\nvzNh+BM9TOJNaTMmVesr4vXNDLOSFS3PxYv95xuOBzVg3zOHuai72g==\n-----END AGE ENCRYPTED FILE-----\n"
16 }
17 ],
18 "lastmodified": "2025-02-13T19:20:33Z",
19 "mac": "ENC[AES256_GCM,data:mG6AC3L8MMeZ0Ajr7zV1mzPcHviQw2adtGjSbrbPRw1xqN7siu6svoybv8xkahP2Grq/xKAiyfXFOFo7Uyc3ub5fSovAEolNazqybZYsyam5vHpeC23dXcEkZUJSPJ9/CSB5uI9nX3NPC64QUjCxHZ7qfH5gcXT9D12H8LSqKlQ=,iv:4Skdj8l9jlTX9Unc2xE2hCKVawHBnHR8L4kZA6H8xNw=,tag:zJsJ3S//faAn7AGwLefNoA==,type:str]",
20 "pgp": null,
21 "unencrypted_suffix": "_unencrypted",
22 "version": "3.9.4"
23 }
24} \ No newline at end of file
diff --git a/hosts/vidhar/prometheus/default.nix b/hosts/vidhar/prometheus/default.nix
index d368ad52..094f9f7a 100644
--- a/hosts/vidhar/prometheus/default.nix
+++ b/hosts/vidhar/prometheus/default.nix
@@ -26,7 +26,8 @@ in {
26 enable = true; 26 enable = true;
27 27
28 extraFlags = [ 28 extraFlags = [
29 "--enable-feature=remote-write-receiver" 29 "--web.enable-remote-write-receiver"
30 "--storage.tsdb.retention.size=35GB"
30 ]; 31 ];
31 32
32 exporters = { 33 exporters = {
diff --git a/installer-profiles/cd-dvd.nix b/installer-profiles/cd-dvd.nix
index 45291bad..ac12d885 100644
--- a/installer-profiles/cd-dvd.nix
+++ b/installer-profiles/cd-dvd.nix
@@ -1,7 +1,13 @@
1{ flakeInputs, ... }: 1{ flakeInputs, lib, ... }:
2 2
3{ 3{
4 imports = [ 4 imports = [
5 "${flakeInputs.nixpkgs.outPath}/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix" 5 "${flakeInputs.nixpkgs.outPath}/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix"
6 ]; 6 ];
7
8 config = {
9 isoImage.squashfsCompression = "zstd -Xcompression-level 9";
10 system.installer.channel.enable = false;
11 boot.loader.grub.memtest86.enable = lib.mkForce false;
12 };
7} 13}
diff --git a/installer-profiles/netboot.nix b/installer-profiles/netboot.nix
index 28e8084d..6e39ebfb 100644
--- a/installer-profiles/netboot.nix
+++ b/installer-profiles/netboot.nix
@@ -4,4 +4,9 @@
4 imports = [ 4 imports = [
5 "${flakeInputs.nixpkgs.outPath}/nixos/modules/installer/netboot/netboot-minimal.nix" 5 "${flakeInputs.nixpkgs.outPath}/nixos/modules/installer/netboot/netboot-minimal.nix"
6 ]; 6 ];
7
8 config = {
9 netboot.squashfsCompression = "zstd -Xcompression-level 9";
10 system.installer.channel.enable = false;
11 };
7} 12}
diff --git a/installer-profiles/nfsroot.nix b/installer-profiles/nfsroot.nix
index 6bd875b4..a8f6def6 100644
--- a/installer-profiles/nfsroot.nix
+++ b/installer-profiles/nfsroot.nix
@@ -8,4 +8,6 @@
8 "${flakeInputs.nixpkgs.outPath}/nixos/modules/profiles/base.nix" 8 "${flakeInputs.nixpkgs.outPath}/nixos/modules/profiles/base.nix"
9 "${flakeInputs.nixpkgs.outPath}/nixos/modules/profiles/installation-device.nix" 9 "${flakeInputs.nixpkgs.outPath}/nixos/modules/profiles/installation-device.nix"
10 ]; 10 ];
11
12 config.system.installer.channel.enable = false;
11} 13}
diff --git a/installer/default.nix b/installer/default.nix
index cd1ee064..26f38572 100644
--- a/installer/default.nix
+++ b/installer/default.nix
@@ -8,7 +8,7 @@ with lib;
8 ]; 8 ];
9 9
10 config = { 10 config = {
11 boot.initrd.availableKernelModules = [ "e1000e" ]; 11 boot.initrd.kernelModules = [ "e1000e" "virtio_net" ];
12 12
13 hardware.cpu.amd.updateMicrocode = config.hardware.enableRedistributableFirmware; 13 hardware.cpu.amd.updateMicrocode = config.hardware.enableRedistributableFirmware;
14 14
@@ -47,7 +47,7 @@ with lib;
47 services.xserver.videoDrivers = [ "nvidia" ]; 47 services.xserver.videoDrivers = [ "nvidia" ];
48 systemd.services.nvidia-control-devices = { 48 systemd.services.nvidia-control-devices = {
49 wantedBy = [ "multi-user.target" ]; 49 wantedBy = [ "multi-user.target" ];
50 serviceConfig.ExecStart = "${pkgs.linuxPackages.nvidia_x11.bin}/bin/nvidia-smi"; 50 serviceConfig.ExecStart = lib.getExe' pkgs.linuxPackages.nvidia_x11.bin "nvidia-smi";
51 }; 51 };
52 nixpkgs.externalConfig.allowUnfree = true; 52 nixpkgs.externalConfig.allowUnfree = true;
53 53
@@ -57,6 +57,10 @@ with lib;
57 57
58 system.disableInstallerTools = false; 58 system.disableInstallerTools = false;
59 59
60 xdg.autostart.enable = lib.mkForce false;
61 xdg.icons.enable = lib.mkForce false;
62 xdg.mime.enable = lib.mkForce false;
63
60 systemd.sysusers.enable = false; 64 systemd.sysusers.enable = false;
61 system.machine-id.generate.enable = false; 65 system.machine-id.generate.enable = false;
62 system.stateVersion = config.system.nixos.release; # No state in installer 66 system.stateVersion = config.system.nixos.release; # No state in installer
diff --git a/lib/pythonSet.nix b/lib/pythonSet.nix
new file mode 100644
index 00000000..c69216cc
--- /dev/null
+++ b/lib/pythonSet.nix
@@ -0,0 +1,42 @@
1{ uv2nix, pyproject-nix, pyproject-build-systems, ... }:
2{ pkgs, python, overlay, lib ? pkgs.lib }:
3(pkgs.callPackage pyproject-nix.build.packages {
4 inherit python;
5}).overrideScope
6 (
7 lib.composeManyExtensions [
8 pyproject-build-systems.overlays.default
9 overlay
10 (final: prev: {
11 sdnotify = (prev.sdnotify.override {
12 sourcePreference = "sdist";
13 }).overrideAttrs (oldAttrs: {
14 nativeBuildInputs = (oldAttrs.nativeBuildInputs or []) ++ [
15 (final.resolveBuildSystem { setuptools = []; })
16 ];
17 });
18 systemd-python = (prev.systemd-python.override {
19 sourcePreference = "sdist";
20 }).overrideAttrs (oldAttrs: {
21 nativeBuildInputs = (oldAttrs.nativeBuildInputs or []) ++ [
22 pkgs.pkg-config pkgs.systemd.dev
23 (final.resolveBuildSystem { setuptools = []; })
24 ];
25 });
26 halo = (prev.halo.override {
27 sourcePreference = "sdist";
28 }).overrideAttrs (oldAttrs: {
29 nativeBuildInputs = (oldAttrs.nativeBuildInputs or []) ++ [
30 (final.resolveBuildSystem { setuptools = []; })
31 ];
32 });
33 unshare = (prev.unshare.override {
34 sourcePreference = "sdist";
35 }).overrideAttrs (oldAttrs: {
36 nativeBuildInputs = (oldAttrs.nativeBuildInputs or []) ++ [
37 (final.resolveBuildSystem { setuptools = []; })
38 ];
39 });
40 })
41 ]
42 )
diff --git a/modules/abs-podcast-autoplaylist.nix b/modules/abs-podcast-autoplaylist.nix
new file mode 100644
index 00000000..f526a434
--- /dev/null
+++ b/modules/abs-podcast-autoplaylist.nix
@@ -0,0 +1,55 @@
1{ config, pkgs, lib, utils, ... }:
2
3let
4 cfg = config.services.abs-podcast-autoplaylist;
5
6 enabledAttrs = lib.filterAttrs (_name: { enable, ... }: enable) cfg;
7in {
8 options = {
9 services.abs-podcast-autoplaylist = lib.mkOption {
10 default = {};
11 type = lib.types.attrsOf (lib.types.submodule ({ name, ... }: {
12 options = {
13 enable = lib.mkEnableOption "this instance of abs-podcast-autoplaylist" // {
14 default = true;
15 };
16 cron = lib.mkOption {
17 type = lib.types.str;
18 default = "*-*-* *:00/30:00";
19 };
20 configSecret = lib.mkOption {
21 type = lib.types.str;
22 default = "abs-podcast-autoplaylist-${name}.toml";
23 };
24 };
25 }));
26 };
27 };
28
29 config = lib.mkIf (enabledAttrs != {}) {
30 systemd.services = {
31 "abs-podcast-autoplaylist@" = {
32 serviceConfig = {
33 WorkingDirectory = "%d";
34 DynamicUser = true;
35 ProtectHome = true;
36 PrivateTmp = true;
37 PrivateDevices = true;
38 Type = "oneshot";
39 ExecStart = "${lib.getExe pkgs.abs-podcast-autoplaylist} %I.toml";
40 TimeoutSec = "5min";
41 };
42 };
43 } // lib.mapAttrs' (name: { configSecret, ... }: lib.nameValuePair "abs-podcast-autoplaylist@${utils.escapeSystemdPath name}" {
44 overrideStrategy = "asDropin";
45 serviceConfig = {
46 LoadCredential = "${name}.toml:${config.sops.secrets.${configSecret}.path}";
47 };
48 }) enabledAttrs;
49
50 systemd.timers = lib.mapAttrs' (name: { cron, ... }: lib.nameValuePair "abs-podcast-autoplaylist@${utils.escapeSystemdPath name}" {
51 wantedBy = [ "timers.target" ];
52 timerConfig.OnCalendar = cron;
53 }) enabledAttrs;
54 };
55}
diff --git a/modules/backup-utils.nix b/modules/backup-utils.nix
index 82a42ecd..698140da 100644
--- a/modules/backup-utils.nix
+++ b/modules/backup-utils.nix
@@ -9,5 +9,8 @@ with lib;
9 9
10 config = { 10 config = {
11 services.borgsnap.archive-prefix = mkDefault "yggdrasil.${hostName}."; 11 services.borgsnap.archive-prefix = mkDefault "yggdrasil.${hostName}.";
12
13 systemd.services."zfssnap-prune".restartIfChanged = false;
14 systemd.services."zfssnap".restartIfChanged = false;
12 }; 15 };
13} 16}
diff --git a/modules/borgcopy/.envrc b/modules/borgcopy/.envrc
new file mode 100644
index 00000000..01e755c1
--- /dev/null
+++ b/modules/borgcopy/.envrc
@@ -0,0 +1,4 @@
1use flake
2
3uv venv && uv sync
4. .venv/bin/activate
diff --git a/modules/borgcopy/.gitignore b/modules/borgcopy/.gitignore
new file mode 100644
index 00000000..4ccfae70
--- /dev/null
+++ b/modules/borgcopy/.gitignore
@@ -0,0 +1,2 @@
1.venv
2**/__pycache__
diff --git a/modules/borgcopy/default.nix b/modules/borgcopy/default.nix
index 475edbd9..af021777 100644
--- a/modules/borgcopy/default.nix
+++ b/modules/borgcopy/default.nix
@@ -1,27 +1,35 @@
1{ config, pkgs, lib, utils, flakeInputs, ... }: 1{ config, pkgs, lib, utils, flake, flakeInputs, ... }:
2 2
3with lib; 3with lib;
4 4
5let 5let
6 copyBorg = 6 copyBorg = let
7 with pkgs.poetry2nix; 7 workspace = flakeInputs.uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./.; };
8 mkPoetryApplication { 8 pythonSet = flake.lib.pythonSet {
9 projectDir = cleanPythonSources { src = ./.; }; 9 inherit pkgs;
10 python = pkgs.python312;
11 overlay = workspace.mkPyprojectOverlay {
12 sourcePreference = "wheel";
13 };
14 };
15 virtualEnv = pythonSet.mkVirtualEnv "copy_borg" workspace.deps.default;
16 in virtualEnv.overrideAttrs (oldAttrs: {
17 meta = (oldAttrs.meta or {}) // {
18 mainProgram = "copy_borg";
19 };
10 20
11 overrides = overrides.withDefaults (self: super: { 21 nativeBuildInputs = (oldAttrs.nativeBuildInputs or []) ++ [ pkgs.makeWrapper ];
12 pyprctl = super.pyprctl.overridePythonAttrs (oldAttrs: {
13 buildInputs = (oldAttrs.buildInputs or []) ++ [super.setuptools];
14 });
15 inherit (pkgs.python3Packages) python-unshare;
16 });
17 22
18 postInstall = '' 23 postInstall = ''
19 wrapProgram $out/bin/copy_borg \ 24 ${oldAttrs.postInstall or ""}
20 --prefix PATH : ${makeBinPath (with pkgs; [util-linux borgbackup])}:${config.security.wrapperDir} 25
21 ''; 26 wrapProgram $out/bin/copy_borg \
22 }; 27 --prefix PATH : ${makeBinPath (with pkgs; [util-linux borgbackup])}:${config.security.wrapperDir}
28 '';
29 });
23 30
24 copyService = name: opts: nameValuePair "copy-borg@${utils.escapeSystemdPath name}" { 31 copyService = name: opts: nameValuePair "copy-borg@${utils.escapeSystemdPath name}" {
32 restartIfChanged = false;
25 serviceConfig = { 33 serviceConfig = {
26 Type = "oneshot"; 34 Type = "oneshot";
27 ExecStart = "${copyBorg}/bin/copy_borg --verbosity ${toString opts.verbosity} ${utils.escapeSystemdExecArgs [opts.from opts.to]}"; 35 ExecStart = "${copyBorg}/bin/copy_borg --verbosity ${toString opts.verbosity} ${utils.escapeSystemdExecArgs [opts.from opts.to]}";
diff --git a/modules/borgcopy/poetry.lock b/modules/borgcopy/poetry.lock
deleted file mode 100644
index 759ecfe9..00000000
--- a/modules/borgcopy/poetry.lock
+++ /dev/null
@@ -1,180 +0,0 @@
1# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand.
2
3[[package]]
4name = "colorama"
5version = "0.4.6"
6description = "Cross-platform colored terminal text."
7category = "main"
8optional = false
9python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
10files = [
11 {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
12 {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
13]
14
15[[package]]
16name = "halo"
17version = "0.0.31"
18description = "Beautiful terminal spinners in Python"
19category = "main"
20optional = false
21python-versions = ">=3.4"
22files = [
23 {file = "halo-0.0.31-py2-none-any.whl", hash = "sha256:5350488fb7d2aa7c31a1344120cee67a872901ce8858f60da7946cef96c208ab"},
24 {file = "halo-0.0.31.tar.gz", hash = "sha256:7b67a3521ee91d53b7152d4ee3452811e1d2a6321975137762eb3d70063cc9d6"},
25]
26
27[package.dependencies]
28colorama = ">=0.3.9"
29log-symbols = ">=0.0.14"
30six = ">=1.12.0"
31spinners = ">=0.0.24"
32termcolor = ">=1.1.0"
33
34[package.extras]
35ipython = ["IPython (==5.7.0)", "ipywidgets (==7.1.0)"]
36
37[[package]]
38name = "humanize"
39version = "4.6.0"
40description = "Python humanize utilities"
41category = "main"
42optional = false
43python-versions = ">=3.7"
44files = [
45 {file = "humanize-4.6.0-py3-none-any.whl", hash = "sha256:401201aca462749773f02920139f302450cb548b70489b9b4b92be39fe3c3c50"},
46 {file = "humanize-4.6.0.tar.gz", hash = "sha256:5f1f22bc65911eb1a6ffe7659bd6598e33dcfeeb904eb16ee1e705a09bf75916"},
47]
48
49[package.extras]
50tests = ["freezegun", "pytest", "pytest-cov"]
51
52[[package]]
53name = "log-symbols"
54version = "0.0.14"
55description = "Colored symbols for various log levels for Python"
56category = "main"
57optional = false
58python-versions = "*"
59files = [
60 {file = "log_symbols-0.0.14-py3-none-any.whl", hash = "sha256:4952106ff8b605ab7d5081dd2c7e6ca7374584eff7086f499c06edd1ce56dcca"},
61 {file = "log_symbols-0.0.14.tar.gz", hash = "sha256:cf0bbc6fe1a8e53f0d174a716bc625c4f87043cc21eb55dd8a740cfe22680556"},
62]
63
64[package.dependencies]
65colorama = ">=0.3.9"
66
67[[package]]
68name = "pyprctl"
69version = "0.1.3"
70description = "An interface to Linux's prctl() syscall written in pure Python using ctypes."
71category = "main"
72optional = false
73python-versions = ">=3.6"
74files = [
75 {file = "pyprctl-0.1.3-py3-none-any.whl", hash = "sha256:6302e5114f078fb33e5799835d0a69e2fc180bb6b28ad073515fa40c5272f1dd"},
76 {file = "pyprctl-0.1.3.tar.gz", hash = "sha256:1fb54d3ab030ec02e4afc38fb9662d6634c12834e91ae7959de56a9c09f69c26"},
77]
78
79[[package]]
80name = "python-dateutil"
81version = "2.8.2"
82description = "Extensions to the standard Python datetime module"
83category = "main"
84optional = false
85python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
86files = [
87 {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
88 {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
89]
90
91[package.dependencies]
92six = ">=1.5"
93
94[[package]]
95name = "python-unshare"
96version = "0.2"
97description = "Python bindings for the Linux unshare() syscall"
98category = "main"
99optional = false
100python-versions = "*"
101files = [
102 {file = "python-unshare-0.2.tar.gz", hash = "sha256:f79b7de441b6c27930b775085a6a4fd2f378b628737aaaebc2a6c519023fd47a"},
103]
104
105[[package]]
106name = "six"
107version = "1.16.0"
108description = "Python 2 and 3 compatibility utilities"
109category = "main"
110optional = false
111python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
112files = [
113 {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
114 {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
115]
116
117[[package]]
118name = "spinners"
119version = "0.0.24"
120description = "Spinners for terminals"
121category = "main"
122optional = false
123python-versions = "*"
124files = [
125 {file = "spinners-0.0.24-py3-none-any.whl", hash = "sha256:2fa30d0b72c9650ad12bbe031c9943b8d441e41b4f5602b0ec977a19f3290e98"},
126 {file = "spinners-0.0.24.tar.gz", hash = "sha256:1eb6aeb4781d72ab42ed8a01dcf20f3002bf50740d7154d12fb8c9769bf9e27f"},
127]
128
129[[package]]
130name = "termcolor"
131version = "2.2.0"
132description = "ANSI color formatting for output in terminal"
133category = "main"
134optional = false
135python-versions = ">=3.7"
136files = [
137 {file = "termcolor-2.2.0-py3-none-any.whl", hash = "sha256:91ddd848e7251200eac969846cbae2dacd7d71c2871e92733289e7e3666f48e7"},
138 {file = "termcolor-2.2.0.tar.gz", hash = "sha256:dfc8ac3f350788f23b2947b3e6cfa5a53b630b612e6cd8965a015a776020b99a"},
139]
140
141[package.extras]
142tests = ["pytest", "pytest-cov"]
143
144[[package]]
145name = "tqdm"
146version = "4.65.0"
147description = "Fast, Extensible Progress Meter"
148category = "main"
149optional = false
150python-versions = ">=3.7"
151files = [
152 {file = "tqdm-4.65.0-py3-none-any.whl", hash = "sha256:c4f53a17fe37e132815abceec022631be8ffe1b9381c2e6e30aa70edc99e9671"},
153 {file = "tqdm-4.65.0.tar.gz", hash = "sha256:1871fb68a86b8fb3b59ca4cdd3dcccbc7e6d613eeed31f4c332531977b89beb5"},
154]
155
156[package.dependencies]
157colorama = {version = "*", markers = "platform_system == \"Windows\""}
158
159[package.extras]
160dev = ["py-make (>=0.1.0)", "twine", "wheel"]
161notebook = ["ipywidgets (>=6)"]
162slack = ["slack-sdk"]
163telegram = ["requests"]
164
165[[package]]
166name = "xdg"
167version = "6.0.0"
168description = "Variables defined by the XDG Base Directory Specification"
169category = "main"
170optional = false
171python-versions = ">=3.7,<4.0"
172files = [
173 {file = "xdg-6.0.0-py3-none-any.whl", hash = "sha256:df3510755b4395157fc04fc3b02467c777f3b3ca383257397f09ab0d4c16f936"},
174 {file = "xdg-6.0.0.tar.gz", hash = "sha256:24278094f2d45e846d1eb28a2ebb92d7b67fc0cab5249ee3ce88c95f649a1c92"},
175]
176
177[metadata]
178lock-version = "2.0"
179python-versions = ">=3.10.0,<3.12"
180content-hash = "3c6b538852447a8f3ae34e1be122716d47e669a2b44f7c5d3d850e5d877353c7"
diff --git a/modules/borgcopy/pyproject.toml b/modules/borgcopy/pyproject.toml
index f3401ed2..d76d73c6 100644
--- a/modules/borgcopy/pyproject.toml
+++ b/modules/borgcopy/pyproject.toml
@@ -1,22 +1,25 @@
1[tool.poetry] 1[project]
2name = "copy_borg" 2name = "copy_borg"
3version = "0.0.0" 3version = "0.0.0"
4authors = ["Gregor Kleen <gkleen@yggdrasil.li>"]
5description = "" 4description = ""
5authors = [{ name = "Gregor Kleen", email = "gkleen@yggdrasil.li" }]
6requires-python = "~=3.12"
7dependencies = [
8 "humanize>=4.6.0,<5",
9 "tqdm>=4.65.0,<5",
10 "python-dateutil>=2.8.2,<3",
11 "xdg>=6.0.0,<7",
12 "pyprctl>=0.1.3,<0.2",
13 "halo>=0.0.31,<0.0.32",
14 "unshare>=0.22",
15]
6 16
7[tool.poetry.scripts] 17[project.scripts]
8copy_borg = "copy_borg.__main__:main" 18copy_borg = "copy_borg.__main__:main"
9 19
10[tool.poetry.dependencies]
11python = ">=3.10.0,<3.12"
12humanize = "^4.6.0"
13tqdm = "^4.65.0"
14python-dateutil = "^2.8.2"
15xdg = "^6.0.0"
16python-unshare = "^0.2"
17pyprctl = "^0.1.3"
18halo = "^0.0.31"
19
20[build-system] 20[build-system]
21requires = ["poetry-core>=1.0.0"] 21requires = ["hatchling"]
22build-backend = "poetry.core.masonry.api" \ No newline at end of file 22build-backend = "hatchling.build"
23
24[tool.hatch.build.targets.wheel]
25packages = ["copy_borg"]
diff --git a/modules/borgcopy/uv.lock b/modules/borgcopy/uv.lock
new file mode 100644
index 00000000..1a282598
--- /dev/null
+++ b/modules/borgcopy/uv.lock
@@ -0,0 +1,146 @@
1version = 1
2revision = 2
3requires-python = ">=3.12, <4"
4
5[[package]]
6name = "colorama"
7version = "0.4.6"
8source = { registry = "https://pypi.org/simple" }
9sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
10wheels = [
11 { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
12]
13
14[[package]]
15name = "copy-borg"
16version = "0.0.0"
17source = { editable = "." }
18dependencies = [
19 { name = "halo" },
20 { name = "humanize" },
21 { name = "pyprctl" },
22 { name = "python-dateutil" },
23 { name = "tqdm" },
24 { name = "unshare" },
25 { name = "xdg" },
26]
27
28[package.metadata]
29requires-dist = [
30 { name = "halo", specifier = ">=0.0.31,<0.0.32" },
31 { name = "humanize", specifier = ">=4.6.0,<5" },
32 { name = "pyprctl", specifier = ">=0.1.3,<0.2" },
33 { name = "python-dateutil", specifier = ">=2.8.2,<3" },
34 { name = "tqdm", specifier = ">=4.65.0,<5" },
35 { name = "unshare", specifier = ">=0.22" },
36 { name = "xdg", specifier = ">=6.0.0,<7" },
37]
38
39[[package]]
40name = "halo"
41version = "0.0.31"
42source = { registry = "https://pypi.org/simple" }
43dependencies = [
44 { name = "colorama" },
45 { name = "log-symbols" },
46 { name = "six" },
47 { name = "spinners" },
48 { name = "termcolor" },
49]
50sdist = { url = "https://files.pythonhosted.org/packages/ee/48/d53580d30b1fabf25d0d1fcc3f5b26d08d2ac75a1890ff6d262f9f027436/halo-0.0.31.tar.gz", hash = "sha256:7b67a3521ee91d53b7152d4ee3452811e1d2a6321975137762eb3d70063cc9d6", size = 11666, upload-time = "2020-11-10T02:36:48.335Z" }
51
52[[package]]
53name = "humanize"
54version = "4.12.3"
55source = { registry = "https://pypi.org/simple" }
56sdist = { url = "https://files.pythonhosted.org/packages/22/d1/bbc4d251187a43f69844f7fd8941426549bbe4723e8ff0a7441796b0789f/humanize-4.12.3.tar.gz", hash = "sha256:8430be3a615106fdfceb0b2c1b41c4c98c6b0fc5cc59663a5539b111dd325fb0", size = 80514, upload-time = "2025-04-30T11:51:07.98Z" }
57wheels = [
58 { url = "https://files.pythonhosted.org/packages/a0/1e/62a2ec3104394a2975a2629eec89276ede9dbe717092f6966fcf963e1bf0/humanize-4.12.3-py3-none-any.whl", hash = "sha256:2cbf6370af06568fa6d2da77c86edb7886f3160ecd19ee1ffef07979efc597f6", size = 128487, upload-time = "2025-04-30T11:51:06.468Z" },
59]
60
61[[package]]
62name = "log-symbols"
63version = "0.0.14"
64source = { registry = "https://pypi.org/simple" }
65dependencies = [
66 { name = "colorama" },
67]
68sdist = { url = "https://files.pythonhosted.org/packages/45/87/e86645d758a4401c8c81914b6a88470634d1785c9ad09823fa4a1bd89250/log_symbols-0.0.14.tar.gz", hash = "sha256:cf0bbc6fe1a8e53f0d174a716bc625c4f87043cc21eb55dd8a740cfe22680556", size = 3211, upload-time = "2019-08-08T06:32:22.538Z" }
69wheels = [
70 { url = "https://files.pythonhosted.org/packages/28/5d/d710c38be68b0fb54e645048fe359c3904cc3cb64b2de9d40e1712bf110c/log_symbols-0.0.14-py3-none-any.whl", hash = "sha256:4952106ff8b605ab7d5081dd2c7e6ca7374584eff7086f499c06edd1ce56dcca", size = 3081, upload-time = "2019-08-08T06:32:20.604Z" },
71]
72
73[[package]]
74name = "pyprctl"
75version = "0.1.3"
76source = { registry = "https://pypi.org/simple" }
77sdist = { url = "https://files.pythonhosted.org/packages/c9/16/6ed71ebcad76c1cd5f22185bcc6b31c0ee62fc5e693b626febea8fedeba3/pyprctl-0.1.3.tar.gz", hash = "sha256:1fb54d3ab030ec02e4afc38fb9662d6634c12834e91ae7959de56a9c09f69c26", size = 18739, upload-time = "2021-10-26T23:52:03.87Z" }
78wheels = [
79 { url = "https://files.pythonhosted.org/packages/bf/5e/62765de39bbce8111fb1f4453a4a804913bf49179fa265fb713ed66c9d15/pyprctl-0.1.3-py3-none-any.whl", hash = "sha256:6302e5114f078fb33e5799835d0a69e2fc180bb6b28ad073515fa40c5272f1dd", size = 20016, upload-time = "2021-10-26T23:52:02.986Z" },
80]
81
82[[package]]
83name = "python-dateutil"
84version = "2.9.0.post0"
85source = { registry = "https://pypi.org/simple" }
86dependencies = [
87 { name = "six" },
88]
89sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
90wheels = [
91 { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
92]
93
94[[package]]
95name = "six"
96version = "1.17.0"
97source = { registry = "https://pypi.org/simple" }
98sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
99wheels = [
100 { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
101]
102
103[[package]]
104name = "spinners"
105version = "0.0.24"
106source = { registry = "https://pypi.org/simple" }
107sdist = { url = "https://files.pythonhosted.org/packages/d3/91/bb331f0a43e04d950a710f402a0986a54147a35818df0e1658551c8d12e1/spinners-0.0.24.tar.gz", hash = "sha256:1eb6aeb4781d72ab42ed8a01dcf20f3002bf50740d7154d12fb8c9769bf9e27f", size = 5308, upload-time = "2020-02-19T21:42:32.326Z" }
108wheels = [
109 { url = "https://files.pythonhosted.org/packages/9f/8e/3310207a68118000ca27ac878b8386123628b335ecb3d4bec4743357f0d1/spinners-0.0.24-py3-none-any.whl", hash = "sha256:2fa30d0b72c9650ad12bbe031c9943b8d441e41b4f5602b0ec977a19f3290e98", size = 5499, upload-time = "2020-02-19T21:42:30.876Z" },
110]
111
112[[package]]
113name = "termcolor"
114version = "3.1.0"
115source = { registry = "https://pypi.org/simple" }
116sdist = { url = "https://files.pythonhosted.org/packages/ca/6c/3d75c196ac07ac8749600b60b03f4f6094d54e132c4d94ebac6ee0e0add0/termcolor-3.1.0.tar.gz", hash = "sha256:6a6dd7fbee581909eeec6a756cff1d7f7c376063b14e4a298dc4980309e55970", size = 14324, upload-time = "2025-04-30T11:37:53.791Z" }
117wheels = [
118 { url = "https://files.pythonhosted.org/packages/4f/bd/de8d508070629b6d84a30d01d57e4a65c69aa7f5abe7560b8fad3b50ea59/termcolor-3.1.0-py3-none-any.whl", hash = "sha256:591dd26b5c2ce03b9e43f391264626557873ce1d379019786f99b0c2bee140aa", size = 7684, upload-time = "2025-04-30T11:37:52.382Z" },
119]
120
121[[package]]
122name = "tqdm"
123version = "4.67.1"
124source = { registry = "https://pypi.org/simple" }
125dependencies = [
126 { name = "colorama", marker = "sys_platform == 'win32'" },
127]
128sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" }
129wheels = [
130 { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" },
131]
132
133[[package]]
134name = "unshare"
135version = "0.22"
136source = { registry = "https://pypi.org/simple" }
137sdist = { url = "https://files.pythonhosted.org/packages/15/85/2ba218129c95b894efe87506489b525f859c40f6e21cb0521ff3cec754f4/unshare-0.22.tar.gz", hash = "sha256:d521d72cca6e876f22cbd5ff5eb51f1beef75e8f9c53b599b55fa05fba1dd3a6", size = 2041, upload-time = "2019-10-17T12:58:31.498Z" }
138
139[[package]]
140name = "xdg"
141version = "6.0.0"
142source = { registry = "https://pypi.org/simple" }
143sdist = { url = "https://files.pythonhosted.org/packages/2a/b9/0e6e6f19fb75cf5e1758f4f33c1256738f718966700cffc0fde2f966218b/xdg-6.0.0.tar.gz", hash = "sha256:24278094f2d45e846d1eb28a2ebb92d7b67fc0cab5249ee3ce88c95f649a1c92", size = 3453, upload-time = "2023-02-27T19:27:44.309Z" }
144wheels = [
145 { url = "https://files.pythonhosted.org/packages/dd/54/3516c1cf349060fc3578686d271eba242f10ec00b4530c2985af9faac49b/xdg-6.0.0-py3-none-any.whl", hash = "sha256:df3510755b4395157fc04fc3b02467c777f3b3ca383257397f09ab0d4c16f936", size = 3855, upload-time = "2023-02-27T19:27:42.151Z" },
146]
diff --git a/modules/envfs.nix b/modules/envfs.nix
deleted file mode 100644
index b5b453a5..00000000
--- a/modules/envfs.nix
+++ /dev/null
@@ -1,77 +0,0 @@
1{ pkgs, config, lib, ... }:
2
3let
4 cfg = config.services.envfs;
5 mounts = {
6 "/usr/bin" = {
7 device = "none";
8 fsType = "envfs";
9 options = [
10 "bind-mount=/bin"
11 "fallback-path=${pkgs.symlinkJoin {
12 name = "fallback-path";
13 inherit (cfg) paths;
14 }}"
15 "nofail"
16 ];
17 };
18 "/bin" = {
19 device = "/usr/bin";
20 fsType = "none";
21 options = [ "bind" "nofail" ];
22 };
23 };
24in {
25 disabledModules = [ "tasks/filesystems/envfs.nix" ];
26
27 options = {
28 services.envfs = {
29 enable = lib.mkEnableOption "Envfs filesystem" // {
30 default = true;
31 description = ''
32 Fuse filesystem that returns symlinks to executables based on the PATH
33 of the requesting process. This is useful to execute shebangs on NixOS
34 that assume hard coded locations in locations like /bin or /usr/bin
35 etc.
36 '';
37 };
38
39 package = lib.mkOption {
40 type = lib.types.package;
41 default = pkgs.envfs;
42 defaultText = lib.literalExpression "pkgs.envfs";
43 description = "Which package to use for the envfs.";
44 };
45
46 paths = lib.mkOption {
47 type = lib.types.listOf lib.types.package;
48 default = [
49 (pkgs.runCommand "fallback-path-environment" {} ''
50 mkdir -p $out
51 ln -s ${config.environment.usrbinenv} $out/env
52 ln -s ${config.environment.binsh} $out/sh
53 '')
54 ];
55 defaultText = lib.literalExpression ''
56 [ (pkgs.runCommand "fallback-path-environment" {} '''
57 mkdir -p $out
58 ln -s ''${config.environment.usrbinenv} $out/env
59 ln -s ''${config.environment.binsh} $out/sh
60 ''')
61 ]
62 '';
63 description = "Extra packages to join into collection of fallback executables in case not other executable is found";
64 };
65 };
66 };
67
68 config = lib.mkIf (cfg.enable) {
69 environment.systemPackages = [ cfg.package ];
70 # we also want these mounts in virtual machines.
71 fileSystems = if config.virtualisation ? qemu then lib.mkVMOverride mounts else mounts;
72
73 # We no longer need those when using envfs
74 system.activationScripts.usrbinenv = lib.mkForce "";
75 system.activationScripts.binsh = lib.mkForce "";
76 };
77}
diff --git a/modules/i18n.nix b/modules/i18n.nix
new file mode 100644
index 00000000..f84e8b64
--- /dev/null
+++ b/modules/i18n.nix
@@ -0,0 +1,156 @@
1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 aggregatedLocales =
9 (builtins.map
10 (l: (lib.replaceStrings [ "utf8" "utf-8" "UTF8" ] [ "UTF-8" "UTF-8" "UTF-8" ] l) + "/UTF-8")
11 (
12 [ config.i18n.defaultLocale ]
13 ++ (lib.optionals (builtins.isList config.i18n.extraLocales) config.i18n.extraLocales)
14 ++ (lib.attrValues (lib.filterAttrs (n: _v: lib.hasPrefix "LC_" n) config.i18n.extraLocaleSettings))
15 )
16 )
17 ++ (lib.optional (builtins.isString config.i18n.extraLocales) config.i18n.extraLocales);
18in
19{
20 disabledModules = [ "config/i18n.nix" ];
21
22 ###### interface
23
24 options = {
25
26 i18n = {
27 glibcLocales = lib.mkOption {
28 type = lib.types.path;
29 default = pkgs.glibcLocales.override {
30 allLocales = lib.any (x: x == "all") config.i18n.supportedLocales;
31 locales = config.i18n.supportedLocales;
32 };
33 defaultText = lib.literalExpression ''
34 pkgs.glibcLocales.override {
35 allLocales = lib.any (x: x == "all") config.i18n.supportedLocales;
36 locales = config.i18n.supportedLocales;
37 }
38 '';
39 example = lib.literalExpression "pkgs.glibcLocales";
40 description = ''
41 Customized pkg.glibcLocales package.
42
43 Changing this option can disable handling of i18n.defaultLocale
44 and supportedLocale.
45 '';
46 };
47
48 defaultLocale = lib.mkOption {
49 type = lib.types.str;
50 default = "en_US.UTF-8";
51 example = "nl_NL.UTF-8";
52 description = ''
53 The default locale. It determines the language for program
54 messages, the format for dates and times, sort order, and so on.
55 It also determines the character set, such as UTF-8.
56 '';
57 };
58
59 extraLocales = lib.mkOption {
60 type = lib.types.either (lib.types.listOf lib.types.str) (lib.types.enum [ "all" ]);
61 default = [ ];
62 example = [ "nl_NL.UTF-8" ];
63 description = ''
64 Additional locales that the system should support, besides the ones
65 configured with {option}`i18n.defaultLocale` and
66 {option}`i18n.extraLocaleSettings`.
67 Set this to `"all"` to install all available locales.
68 '';
69 };
70
71 extraLocaleSettings = lib.mkOption {
72 type = lib.types.attrsOf lib.types.str;
73 default = { };
74 example = {
75 LC_MESSAGES = "en_US.UTF-8";
76 LC_TIME = "de_DE.UTF-8";
77 };
78 description = ''
79 A set of additional system-wide locale settings other than
80 `LANG` which can be configured with
81 {option}`i18n.defaultLocale`.
82 '';
83 };
84
85 supportedLocales = lib.mkOption {
86 type = lib.types.listOf lib.types.str;
87 visible = false;
88 default = lib.unique (
89 [
90 "C.UTF-8/UTF-8"
91 "en_US.UTF-8/UTF-8"
92 ]
93 ++ aggregatedLocales
94 );
95 example = [
96 "en_US.UTF-8/UTF-8"
97 "nl_NL.UTF-8/UTF-8"
98 "nl_NL/ISO-8859-1"
99 ];
100 description = ''
101 List of locales that the system should support. The value
102 `"all"` means that all locales supported by
103 Glibc will be installed. A full list of supported locales
104 can be found at <https://sourceware.org/git/?p=glibc.git;a=blob;f=localedata/SUPPORTED>.
105 '';
106 };
107
108 };
109
110 };
111
112 ###### implementation
113
114 config = {
115 warnings =
116 lib.optional
117 (
118 !(
119 (lib.subtractLists config.i18n.supportedLocales aggregatedLocales) == [ ]
120 || lib.any (x: x == "all") config.i18n.supportedLocales
121 )
122 )
123 ''
124 `i18n.supportedLocales` is deprecated in favor of `i18n.extraLocales`,
125 and it seems you are using `i18n.supportedLocales` and forgot to
126 include some locales specified in `i18n.defaultLocale`,
127 `i18n.extraLocales` or `i18n.extraLocaleSettings`.
128
129 If you're trying to install additional locales not specified in
130 `i18n.defaultLocale` or `i18n.extraLocaleSettings`, consider adding
131 only those locales to `i18n.extraLocales`.
132 '';
133
134 environment.systemPackages =
135 # We increase the priority a little, so that plain glibc in systemPackages can't win.
136 lib.optional (config.i18n.supportedLocales != [ ]) (lib.setPrio (-1) config.i18n.glibcLocales);
137
138 environment.sessionVariables = {
139 LANG = config.i18n.defaultLocale;
140 LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive";
141 } // config.i18n.extraLocaleSettings;
142
143 systemd.globalEnvironment = lib.mkIf (config.i18n.supportedLocales != [ ]) {
144 LOCALE_ARCHIVE = "${config.i18n.glibcLocales}/lib/locale/locale-archive";
145 };
146
147 # ‘/etc/locale.conf’ is used by systemd.
148 environment.etc."locale.conf".source = pkgs.writeText "locale.conf" ''
149 LANG=${config.i18n.defaultLocale}
150 ${lib.concatStringsSep "\n" (
151 lib.mapAttrsToList (n: v: "${n}=${v}") config.i18n.extraLocaleSettings
152 )}
153 '';
154
155 };
156}
diff --git a/modules/installer.nix b/modules/installer.nix
new file mode 100644
index 00000000..3e5c6d5b
--- /dev/null
+++ b/modules/installer.nix
@@ -0,0 +1,56 @@
1{ flake, config, lib, pkgs, ... }:
2
3let
4 cfg = config.installer.links;
5
6 installerOutPath = {
7 "cd-dvd" = _: installerBuild: "${installerBuild.config.system.build.isoImage}/iso";
8 "netboot" = {system, variant}: installerBuild: pkgs.runCommandLocal "${system}-${variant}" {} ''
9 mkdir $out
10 install -m 0444 -t $out \
11 ${installerBuild.config.system.build.netbootRamdisk}/initrd \
12 ${installerBuild.config.system.build.kernel}/${config.system.boot.loader.kernelFile} \
13 ${installerBuild.config.system.build.netbootIpxeScript}/netboot.ipxe \
14 ${pkgs.ipxe.override {
15 additionalTargets = {
16 "bin-i386-efi/ipxe.efi" = "i386-ipxe.efi";
17 };
18 additionalOptions = [
19 "NSLOOKUP_CMD"
20 "PING_CMD"
21 "CONSOLE_CMD"
22 ];
23 embedScript = pkgs.writeText "netboot.ipxe" ''
24 #!ipxe
25
26 chain netboot.ipxe
27 '';
28 }}/{ipxe.efi,i386-ipxe.efi,ipxe.lkrn}
29 '';
30 };
31in {
32 options = {
33 installer.links = lib.mkOption {
34 type = lib.types.listOf (lib.types.submodule {
35 options = {
36 system = lib.mkOption {
37 type = lib.types.str;
38 };
39 variant = lib.mkOption {
40 type = lib.types.str;
41 };
42 };
43 });
44 default = [];
45 };
46 };
47
48 config = lib.mkIf (cfg != []) {
49 systemd.tmpfiles.rules = map (installer'@{system, variant}:
50 let
51 installer = "${system}-${variant}";
52 installerBuild = builtins.addErrorContext "while evaluating installer-${installer}" flake.nixosConfigurations.${"installer-${installer}"};
53 in "L+ /run/installer-${installer} - - - - ${installerOutPath.${variant} installer' installerBuild}"
54 ) cfg;
55 };
56}
diff --git a/modules/pgbackrest.nix b/modules/pgbackrest.nix
index 886840b9..550e970b 100644
--- a/modules/pgbackrest.nix
+++ b/modules/pgbackrest.nix
@@ -43,6 +43,8 @@ let
43 loglevelType = types.enum ["off" "error" "warn" "info" "detail" "debug" "trace"]; 43 loglevelType = types.enum ["off" "error" "warn" "info" "detail" "debug" "trace"];
44 inherit (utils.systemdUtils.unitOptions) unitOption; 44 inherit (utils.systemdUtils.unitOptions) unitOption;
45in { 45in {
46 disabledModules = ["services/backup/pgbackrest.nix"];
47
46 options = { 48 options = {
47 services.pgbackrest = { 49 services.pgbackrest = {
48 enable = mkEnableOption "pgBackRest"; 50 enable = mkEnableOption "pgBackRest";
@@ -216,6 +218,7 @@ in {
216 }; 218 };
217 }; 219 };
218 } // mapAttrs' (name: backupCfg: nameValuePair "pgbackrest-backup@${escapeSystemdPath name}" { 220 } // mapAttrs' (name: backupCfg: nameValuePair "pgbackrest-backup@${escapeSystemdPath name}" {
221 restartIfChanged = false;
219 description = "Perform pgBackRest Backup (${name}${optionalString (!(isNull backupCfg.repo)) " repo${backupCfg.repo}"})"; 222 description = "Perform pgBackRest Backup (${name}${optionalString (!(isNull backupCfg.repo)) " repo${backupCfg.repo}"})";
220 serviceConfig = { 223 serviceConfig = {
221 Type = "oneshot"; 224 Type = "oneshot";
diff --git a/modules/postsrsd.nix b/modules/postsrsd.nix
new file mode 100644
index 00000000..205e669d
--- /dev/null
+++ b/modules/postsrsd.nix
@@ -0,0 +1,157 @@
1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8
9 cfg = config.services.postsrsd;
10 runtimeDirectoryName = "postsrsd";
11 runtimeDirectory = "/run/${runtimeDirectoryName}";
12 # TODO: follow RFC 42, but we need a libconfuse format first:
13 # https://github.com/NixOS/nixpkgs/issues/401565
14 # Arrays in `libconfuse` look like this: {"Life", "Universe", "Everything"}
15 # See https://www.nongnu.org/confuse/tutorial-html/ar01s03.html.
16 #
17 # Note: We're using `builtins.toJSON` to escape strings, but JSON strings
18 # don't have exactly the same semantics as libconfuse strings. For example,
19 # "${F}" gets treated as an env var reference, see above issue for details.
20 libconfuseDomains = "{ " + lib.concatMapStringsSep ", " builtins.toJSON cfg.domains + " }";
21 configFile = pkgs.writeText "postsrsd.conf" ''
22 secrets-file = "''${CREDENTIALS_DIRECTORY}/secrets-file"
23 domains = ${libconfuseDomains}
24 separator = "${cfg.separator}"
25
26 # Disable postsrsd's jailing in favor of confinement with systemd.
27 unprivileged-user = ""
28 chroot-dir = ""
29
30 ${cfg.extraConfig}
31 '';
32
33in
34{
35 imports =
36 map
37 (
38 name:
39 lib.mkRemovedOptionModule [ "services" "postsrsd" name ] ''
40 `postsrsd` was upgraded to `>= 2.0.0`, with some different behaviors and configuration settings:
41 - NixOS Release Notes: https://nixos.org/manual/nixos/unstable/release-notes#sec-nixpkgs-release-25.05-incompatibilities
42 - NixOS Options Reference: https://nixos.org/manual/nixos/unstable/options#opt-services.postsrsd.enable
43 - Migration instructions: https://github.com/roehling/postsrsd/blob/2.0.10/README.rst#migrating-from-version-1x
44 - Postfix Setup: https://github.com/roehling/postsrsd/blob/2.0.10/README.rst#postfix-setup
45 ''
46 )
47 [
48 "domain"
49 "forwardPort"
50 "reversePort"
51 "timeout"
52 "excludeDomains"
53 ];
54
55 disabledModules = [ "services/mail/postsrsd.nix" ];
56
57 options = {
58 services.postsrsd = {
59 enable = lib.mkOption {
60 type = lib.types.bool;
61 default = false;
62 description = "Whether to enable the postsrsd SRS server for Postfix.";
63 };
64
65 secretsFile = lib.mkOption {
66 type = lib.types.path;
67 default = "/var/lib/postsrsd/postsrsd.secret";
68 description = "Secret keys used for signing and verification";
69 };
70
71 domains = lib.mkOption {
72 type = lib.types.listOf lib.types.str;
73 description = "Domain names for rewrite";
74 default = [ config.networking.hostName ];
75 defaultText = lib.literalExpression "[ config.networking.hostName ]";
76 };
77
78 separator = lib.mkOption {
79 type = lib.types.enum [
80 "-"
81 "="
82 "+"
83 ];
84 default = "=";
85 description = "First separator character in generated addresses";
86 };
87
88 user = lib.mkOption {
89 type = lib.types.str;
90 default = "postsrsd";
91 description = "User for the daemon";
92 };
93
94 group = lib.mkOption {
95 type = lib.types.str;
96 default = "postsrsd";
97 description = "Group for the daemon";
98 };
99
100 extraConfig = lib.mkOption {
101 type = lib.types.lines;
102 default = "";
103 };
104 };
105 };
106
107 config = lib.mkIf cfg.enable {
108 users.users = lib.optionalAttrs (cfg.user == "postsrsd") {
109 postsrsd = {
110 group = cfg.group;
111 uid = config.ids.uids.postsrsd;
112 };
113 };
114
115 users.groups = lib.optionalAttrs (cfg.group == "postsrsd") {
116 postsrsd.gid = config.ids.gids.postsrsd;
117 };
118
119 systemd.services.postsrsd-generate-secrets = {
120 path = [ pkgs.coreutils ];
121 script = ''
122 if [ -e "${cfg.secretsFile}" ]; then
123 echo "Secrets file exists. Nothing to do!"
124 else
125 echo "WARNING: secrets file not found, autogenerating!"
126 DIR="$(dirname "${cfg.secretsFile}")"
127 install -m 750 -o ${cfg.user} -g ${cfg.group} -d "$DIR"
128 install -m 600 -o ${cfg.user} -g ${cfg.group} <(dd if=/dev/random bs=18 count=1 | base64) "${cfg.secretsFile}"
129 fi
130 '';
131 serviceConfig = {
132 Type = "oneshot";
133 };
134 };
135
136 systemd.services.postsrsd = {
137 description = "PostSRSd SRS rewriting server";
138 after = [
139 "network.target"
140 "postsrsd-generate-secrets.service"
141 ];
142 before = [ "postfix.service" ];
143 wantedBy = [ "multi-user.target" ];
144 requires = [ "postsrsd-generate-secrets.service" ];
145 confinement.enable = true;
146
147 serviceConfig = {
148 ExecStart = "${lib.getExe pkgs.postsrsd} -C ${configFile}";
149 User = cfg.user;
150 Group = cfg.group;
151 PermissionsStartOnly = true;
152 RuntimeDirectory = runtimeDirectoryName;
153 LoadCredential = "secrets-file:${cfg.secretsFile}";
154 };
155 };
156 };
157}
diff --git a/modules/systemd-run0.nix b/modules/systemd-run0.nix
new file mode 100644
index 00000000..8575ec7c
--- /dev/null
+++ b/modules/systemd-run0.nix
@@ -0,0 +1,4 @@
1{ config, lib, ... }:
2{
3 config.security.pam.services.systemd-run0 = lib.mkIf (lib.versionAtLeast config.systemd.package.version "256") {};
4}
diff --git a/modules/tzupdate.nix b/modules/tzupdate.nix
new file mode 100644
index 00000000..6465d33f
--- /dev/null
+++ b/modules/tzupdate.nix
@@ -0,0 +1,81 @@
1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.services.tzupdate;
9in
10{
11 disabledModules = [ "services/misc/tzupdate.nix" ];
12
13 options.services.tzupdate = {
14 enable = lib.mkOption {
15 type = lib.types.bool;
16 default = false;
17 description = ''
18 Enable the tzupdate timezone updating service. This provides
19 a one-shot service which can be activated with systemctl to
20 update the timezone.
21 '';
22 };
23
24 package = lib.mkPackageOption pkgs "tzupdate" { };
25
26 timer.enable = lib.mkOption {
27 type = lib.types.bool;
28 default = true;
29 description = ''
30 Enable the tzupdate timer to update the timezone automatically.
31 '';
32 };
33
34 timer.interval = lib.mkOption {
35 type = lib.types.str;
36 default = "hourly";
37 description = ''
38 The interval at which the tzupdate timer should run. See
39 {manpage}`systemd.time(7)` to understand the format.
40 '';
41 };
42 };
43
44 config = lib.mkIf cfg.enable {
45 # We need to have imperative time zone management for this to work.
46 # This will give users an error if they have set an explicit time
47 # zone, which is better than silently overriding it.
48 time.timeZone = null;
49
50 # We provide a one-shot service that runs at startup once network
51 # interfaces are up, but we can’t ensure we actually have Internet access
52 # at that point. It can also be run manually with `systemctl start tzupdate`.
53 systemd.services.tzupdate = {
54 description = "tzupdate timezone update service";
55 wantedBy = [ "multi-user.target" ];
56 wants = [ "network-online.target" ];
57 after = [ "network-online.target" ];
58 script = ''
59 timezone="$(${lib.getExe cfg.package} --print-only)"
60 if [[ -n "$timezone" ]]; then
61 echo "Setting timezone to '$timezone'"
62 ${lib.getExe' config.systemd.package "timedatectl"} set-timezone "$timezone"
63 fi
64 '';
65
66 serviceConfig = {
67 Type = "oneshot";
68 };
69 };
70
71 systemd.timers.tzupdate = {
72 enable = cfg.timer.enable;
73 timerConfig = {
74 OnStartupSec = "30s";
75 OnCalendar = cfg.timer.interval;
76 Persistent = true;
77 };
78 wantedBy = [ "timers.target" ];
79 };
80 };
81}
diff --git a/modules/uucp.nix b/modules/uucp.nix
deleted file mode 100644
index 10f7297b..00000000
--- a/modules/uucp.nix
+++ /dev/null
@@ -1,373 +0,0 @@
1{ flake, config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 portSpec = name: node: concatStringsSep "\n" (map (port: ''
7 port ${name}.${port}
8 type pipe
9 protocol ${node.protocols}
10 reliable true
11 command ${pkgs.openssh}/bin/ssh -x -o batchmode=yes ${name}.${port}
12 '') node.hostnames);
13 sysSpec = name: node: ''
14 system ${name}
15 time any
16 chat-seven-bit false
17 chat . ""
18 protocol ${node.protocols}
19 command-path ${concatStringsSep " " cfg.commandPath}
20 commands ${concatStringsSep " " node.commands}
21 ${concatStringsSep "\nalternate\n" (map (port: ''
22 port ${name}.${port}
23 '') node.hostnames)}
24 '';
25 sshConfig = name: node: concatStringsSep "\n" (map (port: ''
26 Host ${name}.${port}
27 Hostname ${port}
28 IdentitiesOnly Yes
29 IdentityFile ${cfg.sshKeyDir}/${name}
30 '') node.hostnames);
31 sshKeyGen = name: node: ''
32 if [[ ! -e ${cfg.sshKeyDir}/${name} ]]; then
33 ${pkgs.openssh}/bin/ssh-keygen ${escapeShellArgs node.generateKey} -f ${cfg.sshKeyDir}/${name}
34 fi
35 '';
36 restrictKey = key: ''
37 restrict,command="${chat}" ${key}
38 '';
39 chat = pkgs.writeScript "chat" ''
40 #!${pkgs.stdenv.shell}
41
42 echo .
43 exec ${config.security.wrapperDir}/uucico
44 '';
45
46 nodeCfg = {
47 options = {
48 commands = mkOption {
49 type = types.listOf types.str;
50 default = cfg.defaultCommands;
51 defaultText = literalExpression "config.services.uucp.defaultCommands";
52 description = "Commands to allow for this remote";
53 };
54
55 protocols = mkOption {
56 type = types.separatedString "";
57 default = cfg.defaultProtocols;
58 defaultText = literalExpression "config.services.uucp.defaultProtocols";
59 description = "UUCP protocols to use for this remote";
60 };
61
62 publicKeys = mkOption {
63 type = types.listOf types.str;
64 default = [];
65 description = "SSH client public keys for this node";
66 };
67
68 generateKey = mkOption {
69 type = types.listOf types.str;
70 default = [ "-t" "ed25519" "-N" "" ];
71 description = "Arguments to pass to `ssh-keygen` to generate a keypair for communication with this host";
72 };
73
74 hostnames = mkOption {
75 type = types.listOf types.str;
76 default = [];
77 description = "Hostnames to try in order when connecting";
78 };
79 };
80 };
81
82 cfg = config.services.uucp;
83in {
84 options = {
85 services.uucp = {
86 enable = mkOption {
87 type = types.bool;
88 default = false;
89 description = ''
90 If enabled we set up an account accesible via uucp over ssh
91 '';
92 };
93
94 nodeName = mkOption {
95 type = types.str;
96 default = "nixos";
97 description = "uucp node name";
98 };
99
100 sshUser = mkOption {
101 type = types.attrs;
102 default = {};
103 description = "Overrides for the local uucp linux-user";
104 };
105
106 extraSSHConfig = mkOption {
107 type = types.str;
108 default = "";
109 description = "Extra SSH config";
110 };
111
112 remoteNodes = mkOption {
113 type = types.attrsOf (types.submodule nodeCfg);
114 default = {};
115 description = ''
116 Ports to set up
117 Names will probably need to be configured in sshConfig
118 '';
119 };
120
121 commandPath = mkOption {
122 type = types.listOf types.path;
123 default = [ "${pkgs.rmail}/bin" ];
124 defaultText = literalExpression ''[ "''${pkgs.rmail}/bin" ]'';
125 description = ''
126 Command search path for all systems
127 '';
128 };
129
130 defaultCommands = mkOption {
131 type = types.listOf types.str;
132 default = ["rmail"];
133 description = "Commands allowed for remotes without explicit override";
134 };
135
136 defaultProtocols = mkOption {
137 type = types.separatedString "";
138 default = "te";
139 description = "UUCP protocol to use within ssh unless overriden";
140 };
141
142 incomingProtocols = mkOption {
143 type = types.separatedString "";
144 default = "te";
145 description = "UUCP protocols to use when called";
146 };
147
148 homeDir = mkOption {
149 type = types.path;
150 default = "/var/uucp";
151 description = "Home of the uucp user";
152 };
153
154 sshKeyDir = mkOption {
155 type = types.path;
156 default = "${cfg.homeDir}/.ssh/";
157 defaultText = literalExpression ''''${config.services.uucp.homeDir}/.ssh/'';
158 description = "Directory to store ssh keypairs";
159 };
160
161 spoolDir = mkOption {
162 type = types.path;
163 default = "/var/spool/uucp";
164 description = "Spool directory";
165 };
166
167 lockDir = mkOption {
168 type = types.path;
169 default = "/var/spool/uucp";
170 description = "Lock directory";
171 };
172
173 pubDir = mkOption {
174 type = types.path;
175 default = "/var/spool/uucppublic";
176 description = "Public directory";
177 };
178
179 logFile = mkOption {
180 type = types.path;
181 default = "/var/log/uucp";
182 description = "Log file";
183 };
184
185 statFile = mkOption {
186 type = types.path;
187 default = "/var/log/uucp.stat";
188 description = "Statistics file";
189 };
190
191 debugFile = mkOption {
192 type = types.path;
193 default = "/var/log/uucp.debug";
194 description = "Debug file";
195 };
196
197 interval = mkOption {
198 type = types.nullOr types.str;
199 default = "1h";
200 description = ''
201 Specification of when to run `uucico' in format used by systemd timers
202 The default is to do so every hour
203 '';
204 };
205
206 nmDispatch = mkOption {
207 type = types.bool;
208 default = config.networking.networkmanager.enable;
209 defaultText = literalExpression "config.networking.networkmanager.enable";
210 description = ''
211 Install a network-manager dispatcher script to automatically
212 call all remotes when networking is available
213 '';
214 };
215
216 extraConfig = mkOption {
217 type = types.lines;
218 default = ''
219 run-uuxqt 1
220 '';
221 description = "Extra configuration to append verbatim to `/etc/uucp/config'";
222 };
223
224 extraSys = mkOption {
225 type = types.lines;
226 default = ''
227 protocol-parameter g packet-size 4096
228 '';
229 description = "Extra configuration to prepend verbatim to `/etc/uucp/sys`";
230 };
231 };
232 };
233
234 config = mkIf cfg.enable {
235 environment.etc."uucp/config" = {
236 text = ''
237 hostname ${cfg.nodeName}
238
239 spool ${cfg.spoolDir}
240 lockdir ${cfg.lockDir}
241 pubdir ${cfg.pubDir}
242 logfile ${cfg.logFile}
243 statfile ${cfg.statFile}
244 debugfile ${cfg.debugFile}
245
246 ${cfg.extraConfig}
247 '';
248 };
249
250 users.groups."uucp" = {};
251 users.users."uucp" = {
252 name = "uucp";
253 group = "uucp";
254 isSystemUser = true;
255 isNormalUser = false;
256 createHome = true;
257 home = cfg.homeDir;
258 description = "User for uucp over ssh";
259 useDefaultShell = true;
260 openssh.authorizedKeys.keys = map restrictKey (concatLists (mapAttrsToList (name: node: node.publicKeys) cfg.remoteNodes));
261 } // cfg.sshUser;
262
263 system.activationScripts."uucp-sshconfig" = ''
264 mkdir -p ${config.users.users."uucp".home}/.ssh
265 chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${config.users.users."uucp".home}/.ssh
266 chmod 700 ${config.users.users."uucp".home}/.ssh
267 ln -fs ${builtins.toFile "ssh-config" ''
268 ${concatStringsSep "\n" (mapAttrsToList sshConfig cfg.remoteNodes)}
269
270 ${cfg.extraSSHConfig}
271 ''} ${config.users.users."uucp".home}/.ssh/config
272
273 mkdir -p ${cfg.sshKeyDir}
274 chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.sshKeyDir}
275 chmod 700 ${cfg.sshKeyDir}
276
277 ${concatStringsSep "\n" (mapAttrsToList sshKeyGen cfg.remoteNodes)}
278 '';
279
280 system.activationScripts."uucp-logs" = ''
281 touch ${cfg.logFile}
282 chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.logFile}
283 chmod 644 ${cfg.logFile}
284 touch ${cfg.statFile}
285 chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.statFile}
286 chmod 644 ${cfg.statFile}
287 touch ${cfg.debugFile}
288 chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.debugFile}
289 chmod 644 ${cfg.debugFile}
290 '';
291
292 environment.etc."uucp/port" = {
293 text = ''
294 port ssh
295 type stdin
296 protocol ${cfg.incomingProtocols}
297 '' + concatStringsSep "\n" (mapAttrsToList portSpec cfg.remoteNodes);
298 };
299 environment.etc."uucp/sys" = {
300 text = cfg.extraSys + "\n" + concatStringsSep "\n" (mapAttrsToList sysSpec cfg.remoteNodes);
301 };
302
303 security.wrappers = let
304 wrapper = p: {
305 name = p;
306 value = {
307 source = "${pkgs.uucp}/bin/${p}";
308 owner = "root";
309 group = "root";
310 setuid = true;
311 setgid = false;
312 };
313 };
314 in listToAttrs (map wrapper ["uucico" "cu" "uucp" "uuname" "uustat" "uux" "uuxqt"]);
315
316 nixpkgs.overlays = [(self: super: {
317 rmail = super.writeShellScriptBin "rmail" ''
318 # Dummy UUCP rmail command for postfix/qmail systems
319
320 IFS=" " read junk from junk junk junk junk junk junk junk relay
321
322 case "$from" in
323 *[@!]*) ;;
324 *) from="$from@$relay";;
325 esac
326
327 exec ${config.security.wrapperDir}/sendmail -G -i -f "$from" -- "$@"
328 '';
329 })];
330
331 environment.systemPackages = with pkgs; [
332 uucp
333 ];
334
335 systemd.services."uucico@" = {
336 serviceConfig = {
337 User = "uucp";
338 Type = "oneshot";
339 ExecStart = "${config.security.wrapperDir}/uucico -D -S %i";
340 };
341 };
342
343 systemd.timers."uucico@" = {
344 timerConfig.OnActiveSec = cfg.interval;
345 timerConfig.OnUnitActiveSec = cfg.interval;
346 };
347
348 systemd.targets."multi-user" = {
349 wants = mapAttrsToList (name: node: "uucico@${name}.timer") cfg.remoteNodes;
350 };
351
352 systemd.kill-user.enable = true;
353 systemd.targets."sleep" = {
354 after = [ "kill-user@uucp.service" ];
355 wants = [ "kill-user@uucp.service" ];
356 };
357
358 networking.networkmanager.dispatcherScripts = optional cfg.nmDispatch {
359 type = "basic";
360 source = pkgs.writeScript "callRemotes.sh" ''
361 #!${pkgs.stdenv.shell}
362
363 shopt -s extglob
364
365 case "''${2}" in
366 (?(vpn-)up)
367 ${concatStringsSep "\n " (mapAttrsToList (name: node: "${pkgs.systemd}/bin/systemctl start uucico@${name}.service") cfg.remoteNodes)}
368 ;;
369 esac
370 '';
371 };
372 };
373}
diff --git a/nvfetcher.toml b/nvfetcher.toml
index e7ba37e5..72c0d99d 100644
--- a/nvfetcher.toml
+++ b/nvfetcher.toml
@@ -110,4 +110,16 @@ fetch.pypi = "yt_dlp"
110 110
111[mako] 111[mako]
112src.git = "https://github.com/emersion/mako" 112src.git = "https://github.com/emersion/mako"
113fetch.git = "https://github.com/emersion/mako" \ No newline at end of file 113fetch.git = "https://github.com/emersion/mako"
114
115[swayosd]
116src.git = "https://github.com/ErikReider/SwayOSD"
117fetch.git = "https://github.com/ErikReider/SwayOSD"
118
119[netbootxyz-efi]
120src.github = "netbootxyz/netboot.xyz"
121fetch.url = "https://github.com/netbootxyz/netboot.xyz/releases/download/$ver/netboot.xyz.efi"
122
123[netbootxyz-lkrn]
124src.github = "netbootxyz/netboot.xyz"
125fetch.url = "https://github.com/netbootxyz/netboot.xyz/releases/download/$ver/netboot.xyz.lkrn"
diff --git a/overlays/abs-podcast-autoplaylist/.envrc b/overlays/abs-podcast-autoplaylist/.envrc
new file mode 100644
index 00000000..2c909235
--- /dev/null
+++ b/overlays/abs-podcast-autoplaylist/.envrc
@@ -0,0 +1,4 @@
1use flake
2
3[[ -d ".venv" ]] || ( uv venv && uv sync )
4. .venv/bin/activate
diff --git a/overlays/abs-podcast-autoplaylist/.gitignore b/overlays/abs-podcast-autoplaylist/.gitignore
new file mode 100644
index 00000000..4ccfae70
--- /dev/null
+++ b/overlays/abs-podcast-autoplaylist/.gitignore
@@ -0,0 +1,2 @@
1.venv
2**/__pycache__
diff --git a/overlays/abs-podcast-autoplaylist/abs_podcast_autoplaylist/__init__.py b/overlays/abs-podcast-autoplaylist/abs_podcast_autoplaylist/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/overlays/abs-podcast-autoplaylist/abs_podcast_autoplaylist/__init__.py
diff --git a/overlays/abs-podcast-autoplaylist/abs_podcast_autoplaylist/__main__.py b/overlays/abs-podcast-autoplaylist/abs_podcast_autoplaylist/__main__.py
new file mode 100644
index 00000000..fd739805
--- /dev/null
+++ b/overlays/abs-podcast-autoplaylist/abs_podcast_autoplaylist/__main__.py
@@ -0,0 +1,107 @@
1import click
2from pathlib import Path
3import tomllib
4import requests
5from urllib.parse import urljoin
6from operator import itemgetter
7import re
8from frozendict import frozendict
9
10class BearerAuth(requests.auth.AuthBase):
11 def __init__(self, token):
12 self.token = token
13 def __call__(self, r):
14 r.headers["authorization"] = "Bearer " + self.token
15 return r
16
17class ABSSession(requests.Session):
18 def __init__(self, config):
19 super().__init__()
20 self.base_url = config['instance']
21 self.auth = BearerAuth(config['api_token'])
22
23 def request(self, method, url, *args, **kwargs):
24 joined_url = urljoin(self.base_url, url)
25 return super().request(method, joined_url, *args, **kwargs)
26
27@click.command()
28@click.argument('config_file', type=click.Path(dir_okay=False, path_type=Path))
29def main(config_file: Path):
30 with config_file.open('rb') as fh:
31 config = tomllib.load(fh)
32
33 with ABSSession(config) as s:
34 libraries = s.get('/api/libraries').json()['libraries']
35 playlists = s.get('/api/playlists').json()['playlists']
36
37 for library_config in config['libraries']:
38 [library] = filter(lambda l: l['name'] == library_config['name'], libraries)
39 filtered_playlists = list(filter(lambda p: p['name'] == library_config['playlist'] and p['libraryId'] == library['id'], playlists))
40 def get_playlist():
41 playlist = None
42 if filtered_playlists:
43 [playlist] = filtered_playlists
44 if not playlist:
45 playlist = s.post('/api/playlists', json={
46 'libraryId': library['id'],
47 'name': library_config['playlist'],
48 }).json()
49 return playlist
50
51 podcasts = dict()
52 items = s.get('/api/libraries/{}/items'.format(library['id'])).json()['results']
53 for item in items:
54 item = s.get('/api/items/{}'.format(item['id']), json={'expanded': True}).json()
55 episodes = list()
56 for episode in sorted(item['media']['episodes'], key = itemgetter('publishedAt')):
57 progress = s.get('/api/me/progress/{}/{}'.format(episode['libraryItemId'], episode['id']))
58 if progress.ok and progress.json()["isFinished"]:
59 continue
60 episodes.append(episode)
61 podcasts[item['media']['metadata']['title']] = list(map(lambda x: frozendict({ 'libraryItemId': x['libraryItemId'], 'episodeId': x['id']}), episodes))
62 def lookup_podcast(expr):
63 expr = re.compile(expr, flags=re.I)
64 matches = filter(lambda t: expr.search(t), podcasts.keys())
65 match list(matches):
66 case [x]:
67 return (x,)
68 case _:
69 raise RuntimeError("No unique match for ‘{}’".format(expr))
70
71 priorities = [
72 [
73 k
74 for item in (section if type(section) is list else [section])
75 for k in lookup_podcast(item)
76 ]
77 for section in library_config['priorities']
78 ]
79
80 playlist_items = list()
81 for section in priorities:
82 while any(map(lambda item: item in podcasts, section)):
83 for item in section:
84 if not item in podcasts:
85 continue
86
87 if not podcasts[item]:
88 del podcasts[item]
89 continue
90
91 playlist_items.append(podcasts[item].pop(0))
92
93 playlist = get_playlist()
94 current_playlist_items = map(lambda item: frozendict({ k: v for k, v in item.items() if k in {'libraryItemId', 'episodeId'}}), playlist['items'])
95
96 if current_playlist_items == playlist_items:
97 continue
98
99 to_remove = set(current_playlist_items) - set(playlist_items)
100 if to_remove:
101 s.post('/api/playlists/{}/batch/remove'.format(playlist['id']), json={'items': list(to_remove)}).raise_for_status()
102 playlist = get_playlist()
103 to_add = set(playlist_items) - set(current_playlist_items)
104 if to_add:
105 s.post('/api/playlists/{}/batch/add'.format(playlist['id']), json={'items': list(to_add)}).raise_for_status()
106
107 r = s.patch('/api/playlists/{}'.format(playlist['id']), json={'items': playlist_items}).raise_for_status()
diff --git a/overlays/abs-podcast-autoplaylist/default.nix b/overlays/abs-podcast-autoplaylist/default.nix
new file mode 100644
index 00000000..843f1b65
--- /dev/null
+++ b/overlays/abs-podcast-autoplaylist/default.nix
@@ -0,0 +1,19 @@
1{ prev, final, flake, flakeInputs, ... }:
2
3let
4 workspace = flakeInputs.uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./.; };
5 pythonSet = flake.lib.pythonSet {
6 pkgs = final;
7 python = final.python312;
8 overlay = workspace.mkPyprojectOverlay {
9 sourcePreference = "wheel";
10 };
11 };
12 virtualEnv = pythonSet.mkVirtualEnv "abs-podcast-autoplaylist-env" workspace.deps.default;
13in {
14 abs-podcast-autoplaylist = virtualEnv.overrideAttrs (oldAttrs: {
15 meta = (oldAttrs.meta or {}) // {
16 mainProgram = "abs-podcast-autoplaylist";
17 };
18 });
19}
diff --git a/overlays/abs-podcast-autoplaylist/pyproject.toml b/overlays/abs-podcast-autoplaylist/pyproject.toml
new file mode 100644
index 00000000..f52a84bc
--- /dev/null
+++ b/overlays/abs-podcast-autoplaylist/pyproject.toml
@@ -0,0 +1,16 @@
1[project]
2name = "abs-podcast-autoplaylist"
3version = "0.1.0"
4requires-python = ">=3.12"
5dependencies = [
6 "click>=8.1.8",
7 "frozendict>=2.4.6",
8 "requests>=2.32.3",
9]
10
11[project.scripts]
12abs-podcast-autoplaylist = "abs_podcast_autoplaylist.__main__:main"
13
14[build-system]
15requires = ["hatchling"]
16build-backend = "hatchling.build"
diff --git a/overlays/abs-podcast-autoplaylist/uv.lock b/overlays/abs-podcast-autoplaylist/uv.lock
new file mode 100644
index 00000000..17de5f0e
--- /dev/null
+++ b/overlays/abs-podcast-autoplaylist/uv.lock
@@ -0,0 +1,129 @@
1version = 1
2revision = 2
3requires-python = ">=3.12"
4
5[[package]]
6name = "abs-podcast-autoplaylist"
7version = "0.1.0"
8source = { editable = "." }
9dependencies = [
10 { name = "click" },
11 { name = "frozendict" },
12 { name = "requests" },
13]
14
15[package.metadata]
16requires-dist = [
17 { name = "click", specifier = ">=8.1.8" },
18 { name = "frozendict", specifier = ">=2.4.6" },
19 { name = "requests", specifier = ">=2.32.3" },
20]
21
22[[package]]
23name = "certifi"
24version = "2025.4.26"
25source = { registry = "https://pypi.org/simple" }
26sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload_time = "2025-04-26T02:12:29.51Z" }
27wheels = [
28 { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload_time = "2025-04-26T02:12:27.662Z" },
29]
30
31[[package]]
32name = "charset-normalizer"
33version = "3.4.2"
34source = { registry = "https://pypi.org/simple" }
35sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload_time = "2025-05-02T08:34:42.01Z" }
36wheels = [
37 { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload_time = "2025-05-02T08:32:33.712Z" },
38 { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload_time = "2025-05-02T08:32:35.768Z" },
39 { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload_time = "2025-05-02T08:32:37.284Z" },
40 { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload_time = "2025-05-02T08:32:38.803Z" },
41 { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload_time = "2025-05-02T08:32:40.251Z" },
42 { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload_time = "2025-05-02T08:32:41.705Z" },
43 { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload_time = "2025-05-02T08:32:43.709Z" },
44 { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload_time = "2025-05-02T08:32:46.197Z" },
45 { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload_time = "2025-05-02T08:32:48.105Z" },
46 { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload_time = "2025-05-02T08:32:49.719Z" },
47 { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload_time = "2025-05-02T08:32:51.404Z" },
48 { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload_time = "2025-05-02T08:32:53.079Z" },
49 { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload_time = "2025-05-02T08:32:54.573Z" },
50 { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload_time = "2025-05-02T08:32:56.363Z" },
51 { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload_time = "2025-05-02T08:32:58.551Z" },
52 { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload_time = "2025-05-02T08:33:00.342Z" },
53 { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload_time = "2025-05-02T08:33:02.081Z" },
54 { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload_time = "2025-05-02T08:33:04.063Z" },
55 { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload_time = "2025-05-02T08:33:06.418Z" },
56 { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload_time = "2025-05-02T08:33:08.183Z" },
57 { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload_time = "2025-05-02T08:33:09.986Z" },
58 { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload_time = "2025-05-02T08:33:11.814Z" },
59 { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload_time = "2025-05-02T08:33:13.707Z" },
60 { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload_time = "2025-05-02T08:33:15.458Z" },
61 { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload_time = "2025-05-02T08:33:17.06Z" },
62 { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload_time = "2025-05-02T08:33:18.753Z" },
63 { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload_time = "2025-05-02T08:34:40.053Z" },
64]
65
66[[package]]
67name = "click"
68version = "8.1.8"
69source = { registry = "https://pypi.org/simple" }
70dependencies = [
71 { name = "colorama", marker = "sys_platform == 'win32'" },
72]
73sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload_time = "2024-12-21T18:38:44.339Z" }
74wheels = [
75 { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload_time = "2024-12-21T18:38:41.666Z" },
76]
77
78[[package]]
79name = "colorama"
80version = "0.4.6"
81source = { registry = "https://pypi.org/simple" }
82sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload_time = "2022-10-25T02:36:22.414Z" }
83wheels = [
84 { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload_time = "2022-10-25T02:36:20.889Z" },
85]
86
87[[package]]
88name = "frozendict"
89version = "2.4.6"
90source = { registry = "https://pypi.org/simple" }
91sdist = { url = "https://files.pythonhosted.org/packages/bb/59/19eb300ba28e7547538bdf603f1c6c34793240a90e1a7b61b65d8517e35e/frozendict-2.4.6.tar.gz", hash = "sha256:df7cd16470fbd26fc4969a208efadc46319334eb97def1ddf48919b351192b8e", size = 316416, upload_time = "2024-10-13T12:15:32.449Z" }
92wheels = [
93 { url = "https://files.pythonhosted.org/packages/04/13/d9839089b900fa7b479cce495d62110cddc4bd5630a04d8469916c0e79c5/frozendict-2.4.6-py311-none-any.whl", hash = "sha256:d065db6a44db2e2375c23eac816f1a022feb2fa98cbb50df44a9e83700accbea", size = 16148, upload_time = "2024-10-13T12:15:26.839Z" },
94 { url = "https://files.pythonhosted.org/packages/ba/d0/d482c39cee2ab2978a892558cf130681d4574ea208e162da8958b31e9250/frozendict-2.4.6-py312-none-any.whl", hash = "sha256:49344abe90fb75f0f9fdefe6d4ef6d4894e640fadab71f11009d52ad97f370b9", size = 16146, upload_time = "2024-10-13T12:15:28.16Z" },
95 { url = "https://files.pythonhosted.org/packages/a5/8e/b6bf6a0de482d7d7d7a2aaac8fdc4a4d0bb24a809f5ddd422aa7060eb3d2/frozendict-2.4.6-py313-none-any.whl", hash = "sha256:7134a2bb95d4a16556bb5f2b9736dceb6ea848fa5b6f3f6c2d6dba93b44b4757", size = 16146, upload_time = "2024-10-13T12:15:29.495Z" },
96]
97
98[[package]]
99name = "idna"
100version = "3.10"
101source = { registry = "https://pypi.org/simple" }
102sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload_time = "2024-09-15T18:07:39.745Z" }
103wheels = [
104 { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload_time = "2024-09-15T18:07:37.964Z" },
105]
106
107[[package]]
108name = "requests"
109version = "2.32.3"
110source = { registry = "https://pypi.org/simple" }
111dependencies = [
112 { name = "certifi" },
113 { name = "charset-normalizer" },
114 { name = "idna" },
115 { name = "urllib3" },
116]
117sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload_time = "2024-05-29T15:37:49.536Z" }
118wheels = [
119 { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload_time = "2024-05-29T15:37:47.027Z" },
120]
121
122[[package]]
123name = "urllib3"
124version = "2.4.0"
125source = { registry = "https://pypi.org/simple" }
126sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload_time = "2025-04-10T15:23:39.232Z" }
127wheels = [
128 { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload_time = "2025-04-10T15:23:37.377Z" },
129]
diff --git a/overlays/cake-prometheus-exporter/default.nix b/overlays/cake-prometheus-exporter/default.nix
index 3d0acc2d..69a5008c 100644
--- a/overlays/cake-prometheus-exporter/default.nix
+++ b/overlays/cake-prometheus-exporter/default.nix
@@ -1,19 +1,18 @@
1{ final, prev, ... }: 1{ final, prev, ... }:
2let 2let
3 inpPython = final.python310.override {}; 3 inpPython = final.python310.override {};
4 python = inpPython.withPackages (ps: with ps; []);
4in { 5in {
5 cake-prometheus-exporter = prev.stdenv.mkDerivation rec { 6 cake-prometheus-exporter = prev.stdenv.mkDerivation rec {
6 pname = "cake-prometheus-exporter"; 7 pname = "cake-prometheus-exporter";
7 version = "0.0.0"; 8 version = "0.0.0";
8 9
9 src = ./cake-prometheus-exporter.py; 10 src = prev.replaceVars ./cake-prometheus-exporter.py { inherit python; };
10 11
11 phases = [ "buildPhase" "checkPhase" "installPhase" ]; 12 phases = [ "unpackPhase" "checkPhase" "installPhase" ];
12 13
13 python = inpPython.withPackages (ps: with ps; []); 14 unpackPhase = ''
14 15 cp $src cake-prometheus-exporter
15 buildPhase = ''
16 substituteAll $src cake-prometheus-exporter
17 ''; 16 '';
18 17
19 doCheck = true; 18 doCheck = true;
diff --git a/overlays/deploy-rs.nix b/overlays/deploy-rs.nix
new file mode 100644
index 00000000..678c6f5f
--- /dev/null
+++ b/overlays/deploy-rs.nix
@@ -0,0 +1,16 @@
1{ final, prev, flakeInputs, ... }: prev.lib.composeExtensions
2 flakeInputs.deploy-rs.overlays.default
3 (final: prev: {
4 deploy-rs = prev.deploy-rs // {
5 deploy-rs = prev.symlinkJoin {
6 name = "${prev.deploy-rs.deploy-rs.name}-wrapped";
7 paths = [ prev.deploy-rs.deploy-rs ];
8 buildInputs = [ prev.makeWrapper ];
9 postBuild = ''
10 wrapProgram $out/bin/deploy \
11 --prefix PATH : ${prev.lib.makeBinPath (with final; [ nix-monitored ])}
12 '';
13 };
14 };
15 })
16 final prev
diff --git a/overlays/etesync-dav.nix b/overlays/etesync-dav.nix
index cec216e2..f7a5146f 100644
--- a/overlays/etesync-dav.nix
+++ b/overlays/etesync-dav.nix
@@ -1,54 +1,29 @@
1{ final, prev, ... }: { 1{ final, prev, ... }: {
2 etesync-dav = prev.python3Packages.buildPythonApplication rec { 2 etesync-dav = final.python3Packages.buildPythonApplication rec {
3 pname = "etesync-dav"; 3 pname = "etesync-dav";
4 version = "0.33.4"; 4 version = "0.35.0";
5 format = "pyproject";
5 6
6 src = prev.fetchFromGitHub { 7 src = prev.fetchFromGitHub {
7 owner = "etesync"; 8 owner = "etesync";
8 repo = "etesync-dav"; 9 repo = "etesync-dav";
9 rev = "v${version}"; 10 tag = "v${version}";
10 hash = "sha256-g+rK762tSWPDaBsaTwpTzfK/lqVs+Z/Qrpq2HCpipQE="; 11 hash = "sha256-CD02nuA9GD/oe7mjExUHIftkPxM1pZQKyDalXSoOhXY=";
11 }; 12 };
12 13
13 dependencies = with prev.python3Packages; [ 14 build-system = with final.python3Packages; [ setuptools ];
15
16 pythonRelaxDeps = [ "radicale" ];
17
18 dependencies = with final.python3Packages; [
14 appdirs 19 appdirs
15 etebase 20 etebase
16 etesync 21 etesync
17 flask 22 flask
18 flask-wtf 23 flask-wtf
19 msgpack 24 msgpack
20 setuptools 25 (final.python3Packages.toPythonModule (final.radicale.override { inherit (final) python3; }))
21 (toPythonModule (buildPythonApplication rec {
22 pname = "radicale";
23 version = "3.2.3";
24 pyproject = true;
25
26 src = prev.fetchFromGitHub {
27 owner = "Kozea";
28 repo = "Radicale";
29 rev = "refs/tags/v${version}";
30 hash = "sha256-1IlnXVetQQuKBt6+QVKNeMM6qBQAiUhqc+4x3xOnSdE=";
31 };
32
33 build-system = [
34 setuptools
35 ];
36
37 dependencies =
38 [
39 defusedxml
40 passlib
41 vobject
42 pika
43 python-dateutil
44 pytz # https://github.com/Kozea/Radicale/issues/816
45 ]
46 ++ passlib.optional-dependencies.bcrypt;
47
48 doCheck = false;
49 }))
50 requests 26 requests
51 types-setuptools
52 requests.optional-dependencies.socks 27 requests.optional-dependencies.socks
53 ]; 28 ];
54 29
diff --git a/overlays/inwx-cdnskey/default.nix b/overlays/inwx-cdnskey/default.nix
index cd564f24..e1bee0f2 100644
--- a/overlays/inwx-cdnskey/default.nix
+++ b/overlays/inwx-cdnskey/default.nix
@@ -2,17 +2,16 @@
2let 2let
3 packageOverrides = final.callPackage ./python-packages.nix {}; 3 packageOverrides = final.callPackage ./python-packages.nix {};
4 inpPython = final.python39.override { inherit packageOverrides; }; 4 inpPython = final.python39.override { inherit packageOverrides; };
5 python = inpPython.withPackages (ps: with ps; [pyxdg inwx-domrobot configparser dnspython]);
5in { 6in {
6 inwx-cdnskey = prev.stdenv.mkDerivation rec { 7 inwx-cdnskey = prev.stdenv.mkDerivation rec {
7 name = "inwx-cdnskey"; 8 name = "inwx-cdnskey";
8 src = ./inwx-cdnskey.py; 9 src = prev.replaceVars ./inwx-cdnskey.py { inherit python; };
9 10
10 phases = [ "buildPhase" "checkPhase" "installPhase" ]; 11 phases = [ "unpackPhase" "checkPhase" "installPhase" ];
11 12
12 python = inpPython.withPackages (ps: with ps; [pyxdg inwx-domrobot configparser dnspython]); 13 unpackPhase = ''
13 14 cp $src inwx-cdnskey
14 buildPhase = ''
15 substituteAll $src inwx-cdnskey
16 ''; 15 '';
17 16
18 doCheck = true; 17 doCheck = true;
diff --git a/overlays/keepassxc/database-open-dialog.patch b/overlays/keepassxc/database-open-dialog.patch
new file mode 100644
index 00000000..dff84846
--- /dev/null
+++ b/overlays/keepassxc/database-open-dialog.patch
@@ -0,0 +1,129 @@
1diff --git a/src/browser/BrowserService.cpp b/src/browser/BrowserService.cpp
2index 60412b5a..c0497d91 100644
3--- a/src/browser/BrowserService.cpp
4+++ b/src/browser/BrowserService.cpp
5@@ -249,7 +249,7 @@ QJsonObject BrowserService::createNewGroup(const QString& groupName)
6 return result;
7 }
8
9- auto dialogResult = MessageBox::warning(m_currentDatabaseWidget,
10+ auto dialogResult = MessageBox::warning(nullptr,
11 tr("KeePassXC - Create a new group"),
12 tr("A request for creating a new group \"%1\" has been received.\n"
13 "Do you want to create this group?\n")
14@@ -422,7 +422,7 @@ QList<Entry*> BrowserService::confirmEntries(QList<Entry*>& entriesToConfirm,
15
16 m_dialogActive = true;
17 updateWindowState();
18- BrowserAccessControlDialog accessControlDialog(m_currentDatabaseWidget);
19+ BrowserAccessControlDialog accessControlDialog{};
20
21 connect(m_currentDatabaseWidget, SIGNAL(databaseLockRequested()), &accessControlDialog, SLOT(reject()));
22
23@@ -512,7 +512,7 @@ QString BrowserService::storeKey(const QString& key)
24 QString id;
25
26 do {
27- QInputDialog keyDialog(m_currentDatabaseWidget);
28+ QInputDialog keyDialog{};
29 connect(m_currentDatabaseWidget, SIGNAL(databaseLockRequested()), &keyDialog, SLOT(reject()));
30 keyDialog.setWindowTitle(tr("KeePassXC - New key association request"));
31 keyDialog.setLabelText(tr("You have received an association request for the following database:\n%1\n\n"
32@@ -535,7 +535,7 @@ QString BrowserService::storeKey(const QString& key)
33
34 contains = db->metadata()->customData()->contains(CustomData::BrowserKeyPrefix + id);
35 if (contains) {
36- dialogResult = MessageBox::warning(m_currentDatabaseWidget,
37+ dialogResult = MessageBox::warning(nullptr,
38 tr("KeePassXC - Overwrite existing key?"),
39 tr("A shared encryption key with the name \"%1\" "
40 "already exists.\nDo you want to overwrite it?")
41@@ -595,7 +595,7 @@ QJsonObject BrowserService::showPasskeysRegisterPrompt(const QJsonObject& public
42 const auto existingEntries = getPasskeyEntriesWithUserHandle(rpId, userId, keyList);
43
44 raiseWindow();
45- BrowserPasskeysConfirmationDialog confirmDialog(m_currentDatabaseWidget);
46+ BrowserPasskeysConfirmationDialog confirmDialog{};
47 confirmDialog.registerCredential(username, rpId, existingEntries, timeout);
48
49 auto dialogResult = confirmDialog.exec();
50@@ -612,7 +612,7 @@ QJsonObject BrowserService::showPasskeysRegisterPrompt(const QJsonObject& public
51 // If no entry is selected, show the import dialog for manual entry selection
52 auto selectedEntry = confirmDialog.getSelectedEntry();
53 if (!selectedEntry) {
54- PasskeyImporter passkeyImporter(m_currentDatabaseWidget);
55+ PasskeyImporter passkeyImporter{};
56 const auto result = passkeyImporter.showImportDialog(db,
57 nullptr,
58 origin,
59@@ -683,7 +683,7 @@ QJsonObject BrowserService::showPasskeysAuthenticationPrompt(const QJsonObject&
60 const auto timeout = publicKeyOptions["timeout"].toInt();
61
62 raiseWindow();
63- BrowserPasskeysConfirmationDialog confirmDialog(m_currentDatabaseWidget);
64+ BrowserPasskeysConfirmationDialog confirmDialog{};
65 confirmDialog.authenticateCredential(entries, rpId, timeout);
66 auto dialogResult = confirmDialog.exec();
67 if (dialogResult == QDialog::Accepted) {
68@@ -760,7 +760,7 @@ void BrowserService::addPasskeyToEntry(Entry* entry,
69
70 // Ask confirmation if entry already contains a Passkey
71 if (entry->hasPasskey()) {
72- if (MessageBox::question(m_currentDatabaseWidget,
73+ if (MessageBox::question(nullptr,
74 tr("KeePassXC - Update passkey"),
75 tr("Entry already has a passkey.\nDo you want to overwrite the passkey in %1 - %2?")
76 .arg(entry->title(), passkeyUtils()->getUsernameFromEntry(entry)),
77@@ -873,7 +873,7 @@ bool BrowserService::updateEntry(const EntryParameters& entryParameters, const Q
78 MessageBox::Button dialogResult = MessageBox::No;
79 if (!browserSettings()->alwaysAllowUpdate()) {
80 raiseWindow();
81- dialogResult = MessageBox::question(m_currentDatabaseWidget,
82+ dialogResult = MessageBox::question(nullptr,
83 tr("KeePassXC - Update Entry"),
84 tr("Do you want to update the information in %1 - %2?")
85 .arg(QUrl(entryParameters.siteUrl).host(), username),
86@@ -909,7 +909,7 @@ bool BrowserService::deleteEntry(const QString& uuid)
87 return false;
88 }
89
90- auto dialogResult = MessageBox::warning(m_currentDatabaseWidget,
91+ auto dialogResult = MessageBox::warning(nullptr,
92 tr("KeePassXC - Delete entry"),
93 tr("A request for deleting entry \"%1\" has been received.\n"
94 "Do you want to delete the entry?\n")
95@@ -1536,7 +1536,7 @@ QSharedPointer<Database> BrowserService::selectedDatabase()
96 }
97 }
98
99- BrowserEntrySaveDialog browserEntrySaveDialog(m_currentDatabaseWidget);
100+ BrowserEntrySaveDialog browserEntrySaveDialog{};
101 int openDatabaseCount = browserEntrySaveDialog.setItems(databaseWidgets, m_currentDatabaseWidget);
102 if (openDatabaseCount > 1) {
103 int res = browserEntrySaveDialog.exec();
104diff --git a/src/fdosecrets/objects/Prompt.cpp b/src/fdosecrets/objects/Prompt.cpp
105index e89cd499..347c98b8 100644
106--- a/src/fdosecrets/objects/Prompt.cpp
107+++ b/src/fdosecrets/objects/Prompt.cpp
108@@ -313,7 +313,7 @@ namespace FdoSecrets
109 if (!entries.isEmpty()) {
110 QString app = tr("%1 (PID: %2)").arg(client->name()).arg(client->pid());
111 auto ac = new AccessControlDialog(
112- findWindow(m_windowId), entries, app, client->processInfo(), AuthOption::Remember);
113+ nullptr, entries, app, client->processInfo(), AuthOption::Remember);
114 connect(ac, &AccessControlDialog::finished, this, &UnlockPrompt::itemUnlockFinished);
115 connect(ac, &AccessControlDialog::finished, ac, &AccessControlDialog::deleteLater);
116 ac->open();
117diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp
118index 805d4eab..4836199e 100644
119--- a/src/gui/DatabaseTabWidget.cpp
120+++ b/src/gui/DatabaseTabWidget.cpp
121@@ -41,7 +41,7 @@ DatabaseTabWidget::DatabaseTabWidget(QWidget* parent)
122 : QTabWidget(parent)
123 , m_dbWidgetStateSync(new DatabaseWidgetStateSync(this))
124 , m_dbWidgetPendingLock(nullptr)
125- , m_databaseOpenDialog(new DatabaseOpenDialog(this))
126+ , m_databaseOpenDialog(new DatabaseOpenDialog())
127 , m_databaseOpenInProgress(false)
128 {
129 auto* tabBar = new QTabBar(this);
diff --git a/overlays/keepassxc/default.nix b/overlays/keepassxc/default.nix
new file mode 100644
index 00000000..46b3a459
--- /dev/null
+++ b/overlays/keepassxc/default.nix
@@ -0,0 +1,8 @@
1{ final, prev, ... }:
2{
3 keepassxc = prev.keepassxc.overrideAttrs (oldAttrs: {
4 patches = (oldAttrs.patches or []) ++ prev.lib.optional (prev.lib.versionAtLeast oldAttrs.version "2.7.9") [
5 ./database-open-dialog.patch
6 ];
7 });
8}
diff --git a/overlays/lesspipe.nix b/overlays/lesspipe.nix
index 3258eb70..b791f6e5 100644
--- a/overlays/lesspipe.nix
+++ b/overlays/lesspipe.nix
@@ -17,7 +17,7 @@
17 17
18 preFixup = '' 18 preFixup = ''
19 wrapProgram $out/bin/lesspipe.sh \ 19 wrapProgram $out/bin/lesspipe.sh \
20 --prefix PATH : ${final.python3.pkgs.pygments}/bin:${final.file}/bin:${final.ncurses}/bin 20 --prefix PATH : ${prev.lib.makeBinPath (with final; [ file ncurses binutils ])}
21 ''; 21 '';
22 }; 22 };
23} 23}
diff --git a/overlays/nftables-prometheus-exporter/default.nix b/overlays/nftables-prometheus-exporter/default.nix
index aab0c8e9..48f668c4 100644
--- a/overlays/nftables-prometheus-exporter/default.nix
+++ b/overlays/nftables-prometheus-exporter/default.nix
@@ -1,17 +1,16 @@
1{ final, prev, ... }: 1{ final, prev, ... }:
2let 2let
3 inpPython = final.python310; 3 inpPython = final.python310;
4 python = inpPython.withPackages (ps: with ps; []);
4in { 5in {
5 nftables-prometheus-exporter = prev.stdenv.mkDerivation rec { 6 nftables-prometheus-exporter = prev.stdenv.mkDerivation rec {
6 name = "nftables-prometheus-exporter"; 7 name = "nftables-prometheus-exporter";
7 src = ./nftables-prometheus-exporter.py; 8 src = prev.replaceVars ./nftables-prometheus-exporter.py { inherit python; };
8 9
9 phases = [ "buildPhase" "checkPhase" "installPhase" ]; 10 phases = [ "unpackPhase" "checkPhase" "installPhase" ];
10 11
11 python = inpPython.withPackages (ps: with ps; []); 12 unpackPhase = ''
12 13 cp $src nftables-prometheus-exporter
13 buildPhase = ''
14 substituteAll $src nftables-prometheus-exporter
15 ''; 14 '';
16 15
17 doCheck = true; 16 doCheck = true;
diff --git a/overlays/niri.nix b/overlays/niri.nix
new file mode 100644
index 00000000..9188ed7d
--- /dev/null
+++ b/overlays/niri.nix
@@ -0,0 +1,8 @@
1{ final, prev, flakeInputs, ... }: prev.lib.composeExtensions
2 flakeInputs.niri-flake.overlays.niri
3 (final: prev: {
4 niri-unstable = prev.niri-unstable.overrideAttrs (oldAttrs: {
5 buildInputs = (oldAttrs.buildInputs or []) ++ [ final.libgbm ];
6 });
7 })
8 final prev
diff --git a/overlays/nix-direnv/default.nix b/overlays/nix-direnv/default.nix
new file mode 100644
index 00000000..7c488e4e
--- /dev/null
+++ b/overlays/nix-direnv/default.nix
@@ -0,0 +1,53 @@
1{ final, prev, ... }: {
2 nix-direnv = prev.resholve.mkDerivation rec {
3 pname = "nix-direnv";
4 version = "3.0.6";
5
6 patches = [
7 ./static-nix.patch
8 ];
9
10 src = prev.fetchFromGitHub {
11 owner = "nix-community";
12 repo = "nix-direnv";
13 rev = version;
14 hash = "sha256-oNqhPqgQT92yxbKmcgX4F3e2yTUPyXYG7b2xQm3TvQw=";
15 };
16
17 installPhase = ''
18 runHook preInstall
19 install -m400 -D direnvrc $out/share/nix-direnv/direnvrc
20 runHook postInstall
21 '';
22
23 solutions = {
24 default = {
25 scripts = [ "share/nix-direnv/direnvrc" ];
26 interpreter = "none";
27 inputs = with final; [ coreutils nix-monitored ];
28 fake = {
29 builtin = [
30 "PATH_add"
31 "direnv_layout_dir"
32 "has"
33 "log_error"
34 "log_status"
35 "watch_file"
36 ];
37 function = [
38 # not really a function - this is in an else branch for macOS/homebrew that
39 # cannot be reached when built with nix
40 "shasum"
41 ];
42 };
43 keep = {
44 "$cmd" = true;
45 "$direnv" = true;
46 };
47 execer = [
48 "cannot:${prev.lib.getExe' final.nix-monitored "nix"}"
49 ];
50 };
51 };
52 };
53}
diff --git a/overlays/nix-direnv/static-nix.patch b/overlays/nix-direnv/static-nix.patch
new file mode 100644
index 00000000..5de8193f
--- /dev/null
+++ b/overlays/nix-direnv/static-nix.patch
@@ -0,0 +1,62 @@
1diff --git i/direnvrc w/direnvrc
2index ddac0f5..fbcade6 100644
3--- i/direnvrc
4+++ w/direnvrc
5@@ -29,10 +29,8 @@ _nix_direnv_warning() {
6
7 _nix_direnv_error() { log_error "${_NIX_DIRENV_LOG_PREFIX}$*"; }
8
9-_nix_direnv_nix=""
10-
11 _nix() {
12- ${_nix_direnv_nix} --extra-experimental-features "nix-command flakes" "$@"
13+ nix --extra-experimental-features "nix-command flakes" "$@"
14 }
15
16 _require_version() {
17@@ -55,34 +53,6 @@ _require_cmd_version() {
18 _require_version "$cmd" "${BASH_REMATCH[1]}" "$required"
19 }
20
21-_nix_direnv_resolve_nix() {
22- local ambient_nix
23-
24- if ambient_nix=$(command -v nix); then
25- if _require_cmd_version "${ambient_nix}" "${NIX_MIN_VERSION}"; then
26- echo "${ambient_nix}"
27- return 0
28- else
29- _nix_direnv_warning "Nix version in PATH is too old, wanted ${NIX_MIN_VERSION}+, got $(${ambient_nix} --version), will attempt fallback"
30- fi
31- else
32- _nix_direnv_warning "Could not find Nix in PATH, will attempt fallback"
33- fi
34-
35- if [ -n "${NIX_DIRENV_FALLBACK_NIX}" ]; then
36- if _require_cmd_version "${NIX_DIRENV_FALLBACK_NIX}" "${NIX_MIN_VERSION}"; then
37- echo "${NIX_DIRENV_FALLBACK_NIX}"
38- return 0
39- else
40- _nix_direnv_error "Fallback Nix version is too old, wanted ${NIX_MIN_VERSION}+, got $(${NIX_DIRENV_FALLBACK_NIX} --version)"
41- return 1
42- fi
43- else
44- _nix_direnv_error "Could not find fallback Nix binary, please add Nix to PATH or set NIX_DIRENV_FALLBACK_NIX"
45- return 1
46- fi
47-}
48-
49 _nix_direnv_preflight() {
50 if [[ -z $direnv ]]; then
51 # shellcheck disable=2016
52@@ -102,10 +72,6 @@ _nix_direnv_preflight() {
53 fi
54 fi
55
56- if ! _nix_direnv_nix=$(_nix_direnv_resolve_nix); then
57- return 1
58- fi
59-
60 local layout_dir
61 layout_dir=$(direnv_layout_dir)
62
diff --git a/overlays/nix-monitored.nix b/overlays/nix-monitored.nix
new file mode 100644
index 00000000..9f6caff1
--- /dev/null
+++ b/overlays/nix-monitored.nix
@@ -0,0 +1,8 @@
1{ final, prev, flakeInputs, ... }: prev.lib.composeExtensions
2 flakeInputs.nix-monitored.overlays.default
3 (final: prev: {
4 nix-monitored = prev.nix-monitored.override {
5 withNotify = false;
6 };
7 })
8 final prev
diff --git a/overlays/persistent-nix-shell/default.nix b/overlays/persistent-nix-shell/default.nix
index c36b9e86..6067cade 100644
--- a/overlays/persistent-nix-shell/default.nix
+++ b/overlays/persistent-nix-shell/default.nix
@@ -5,10 +5,9 @@
5 5
6 phases = [ "buildPhase" "installPhase" ]; 6 phases = [ "buildPhase" "installPhase" ];
7 7
8 inherit (final) zsh;
9
10 buildPhase = '' 8 buildPhase = ''
11 substituteAll $src persistent-nix-shell 9 substitute $src persistent-nix-shell \
10 --subst-var-by zsh ${final.zsh}
12 ''; 11 '';
13 12
14 installPhase = '' 13 installPhase = ''
diff --git a/overlays/postsrsd.nix b/overlays/postsrsd.nix
new file mode 100644
index 00000000..cb1ccf30
--- /dev/null
+++ b/overlays/postsrsd.nix
@@ -0,0 +1,11 @@
1{ final, prev, ... }:
2{
3 postsrsd = prev.postsrsd.overrideAttrs (oldAttrs: {
4 cmakeFlags = (oldAttrs.cmakeFlags or []) ++ [
5 "-DWITH_MILTER=ON"
6 ];
7 buildInputs = (oldAttrs.buildInputs or []) ++ [
8 final.libmilter
9 ];
10 });
11}
diff --git a/overlays/preserve-dscp/default.nix b/overlays/preserve-dscp/default.nix
index 105eccb9..208d69db 100644
--- a/overlays/preserve-dscp/default.nix
+++ b/overlays/preserve-dscp/default.nix
@@ -15,7 +15,7 @@
15 15
16 outputs = [ "out" "lib" ]; 16 outputs = [ "out" "lib" ];
17 17
18 buildInputs = with final; [ elfutils libpcap zlib ]; 18 buildInputs = with final; [ elfutils libpcap libcap zlib ];
19 nativeBuildInputs = with final; [ llvmPackages.clang llvmPackages.llvm pkg-config bpftools libmnl gnum4 glibc_multi makeWrapper ]; 19 nativeBuildInputs = with final; [ llvmPackages.clang llvmPackages.llvm pkg-config bpftools libmnl gnum4 glibc_multi makeWrapper ];
20 20
21 installPhase = '' 21 installPhase = ''
diff --git a/overlays/prometheus-lvm-exporter.nix b/overlays/prometheus-lvm-exporter.nix
index 240f8d85..cae45003 100644
--- a/overlays/prometheus-lvm-exporter.nix
+++ b/overlays/prometheus-lvm-exporter.nix
@@ -3,7 +3,7 @@
3 pname = "prometheus-lvm-exporter"; 3 pname = "prometheus-lvm-exporter";
4 inherit (sources.prometheus-lvm-exporter) version src; 4 inherit (sources.prometheus-lvm-exporter) version src;
5 5
6 vendorHash = "sha256-z/fV0PzoWSDTJ44En19o7zJPPPox5ymFw7sw0Ab9t00="; 6 vendorHash = "sha256-QpJM11Rg+TUV0GWYlvIkwuQqiTsfSdQNtPpD6zT08ew=";
7 7
8 doCheck = false; 8 doCheck = false;
9 9
diff --git a/overlays/scutiger.nix b/overlays/scutiger.nix
index 7a56adaf..fea15c5f 100644
--- a/overlays/scutiger.nix
+++ b/overlays/scutiger.nix
@@ -2,7 +2,7 @@
2 scutiger = final.rustPlatform.buildRustPackage { 2 scutiger = final.rustPlatform.buildRustPackage {
3 inherit (sources.scutiger) pname version src; 3 inherit (sources.scutiger) pname version src;
4 4
5 cargoHash = "sha256-d+wJ3trrldCVATsudsbglElU6q4LaS6feRocRyHal2k="; 5 cargoHash = if prev.lib.versionOlder prev.lib.version "24.05" then "sha256-d+wJ3trrldCVATsudsbglElU6q4LaS6feRocRyHal2k=" else "sha256-FTAEmRuO95ii84uwaALVuImiymnSAQkB2UwZ5yX0WPs=";
6 6
7 nativeBuildInputs = with final; [ pkg-config pcre2.dev zlib.dev git ]; 7 nativeBuildInputs = with final; [ pkg-config pcre2.dev zlib.dev git ];
8 }; 8 };
diff --git a/overlays/spice-record.nix b/overlays/spice-record.nix
index 06a114da..2d37079b 100644
--- a/overlays/spice-record.nix
+++ b/overlays/spice-record.nix
@@ -8,5 +8,7 @@
8 wrapProgram $out/bin/spice-record \ 8 wrapProgram $out/bin/spice-record \
9 --prefix PATH : ${prev.lib.makeBinPath (with prev; [ ffmpeg-full ])} 9 --prefix PATH : ${prev.lib.makeBinPath (with prev; [ ffmpeg-full ])}
10 ''; 10 '';
11 pyproject = true;
12 build-system = [ prev.python3Packages.setuptools ];
11 }; 13 };
12} 14}
diff --git a/overlays/swayosd/default.nix b/overlays/swayosd/default.nix
new file mode 100644
index 00000000..5e715dae
--- /dev/null
+++ b/overlays/swayosd/default.nix
@@ -0,0 +1,13 @@
1{ final, prev, sources, ... }: {
2 swayosd = prev.swayosd.overrideAttrs (oldAttrs: rec {
3 inherit (sources.swayosd) version src;
4 cargoDeps = prev.rustPlatform.fetchCargoVendor {
5 inherit (oldAttrs) pname;
6 inherit version src;
7 hash = "sha256-J2sl6/4+bRWlkvaTJtFsMqvvOxYtWLRjJcYWcu0loRE=";
8 };
9 patches = (oldAttrs.patches or []) ++ [
10 ./exponential.patch
11 ];
12 });
13}
diff --git a/overlays/swayosd/exponential.patch b/overlays/swayosd/exponential.patch
new file mode 100644
index 00000000..eb90d739
--- /dev/null
+++ b/overlays/swayosd/exponential.patch
@@ -0,0 +1,57 @@
1diff --git a/src/brightness_backend/brightnessctl.rs b/src/brightness_backend/brightnessctl.rs
2index ccb0e11..740fdb6 100644
3--- a/src/brightness_backend/brightnessctl.rs
4+++ b/src/brightness_backend/brightnessctl.rs
5@@ -107,10 +107,21 @@ impl VirtualDevice {
6 }
7 }
8
9- fn set_percent(&mut self, mut val: u32) -> anyhow::Result<()> {
10- val = val.clamp(0, 100);
11- self.current = self.max.map(|max| val * max / 100);
12- let _: String = self.run(("set", &*format!("{val}%")))?;
13+ fn val_to_percent(&mut self, val: u32) -> u32 {
14+ return ((val as f64 / self.get_max() as f64).powf(0.25) * 100_f64).round() as u32;
15+ }
16+ fn percent_to_val(&mut self, perc: u32) -> u32 {
17+ return ((perc as f64 / 100_f64).powf(4_f64) * self.get_max() as f64).round() as u32;
18+ }
19+
20+ fn set_percent(&mut self, val: u32) -> anyhow::Result<()> {
21+ let new = self.percent_to_val(val);
22+ self.set_val(new)
23+ }
24+ fn set_val(&mut self, val: u32) -> anyhow::Result<()> {
25+ let curr = val.clamp(0, self.get_max());
26+ self.current = Some(curr);
27+ let _: String = self.run(("set", &*format!("{curr}")))?;
28 Ok(())
29 }
30 }
31@@ -134,20 +145,18 @@ impl BrightnessBackend for BrightnessCtl {
32
33 fn lower(&mut self, by: u32) -> anyhow::Result<()> {
34 let curr = self.get_current();
35- let max = self.get_max();
36-
37- let curr = curr * 100 / max;
38+ let mut new = self.device.val_to_percent(curr).saturating_sub(by);
39+ new = self.device.percent_to_val(new).min(curr.saturating_sub(1));
40
41- self.device.set_percent(curr.saturating_sub(by))
42+ self.device.set_val(new)
43 }
44
45 fn raise(&mut self, by: u32) -> anyhow::Result<()> {
46 let curr = self.get_current();
47- let max = self.get_max();
48-
49- let curr = curr * 100 / max;
50+ let mut new = self.device.val_to_percent(curr) + by;
51+ new = self.device.percent_to_val(new).max(curr + 1);
52
53- self.device.set_percent(curr + by)
54+ self.device.set_val(new)
55 }
56
57 fn set(&mut self, val: u32) -> anyhow::Result<()> {
diff --git a/overlays/uucp/default.nix b/overlays/uucp/default.nix
deleted file mode 100644
index 4189dbcc..00000000
--- a/overlays/uucp/default.nix
+++ /dev/null
@@ -1,9 +0,0 @@
1{ final, prev, ... }: {
2 uucp = prev.uucp.overrideAttrs (oldAttrs: {
3 configureFlags = (oldAttrs.configureFlags or []) ++ ["--with-newconfigdir=/etc/uucp"];
4 patches = (oldAttrs.patches or []) ++ [
5 ./mailprogram.patch
6 ];
7 NIX_CFLAGS_COMPILE = "${oldAttrs.NIX_CFLAGS_COMPILE or ""} -Wno-error=incompatible-pointer-types";
8 });
9}
diff --git a/overlays/uucp/mailprogram.patch b/overlays/uucp/mailprogram.patch
deleted file mode 100644
index 89ac8f31..00000000
--- a/overlays/uucp/mailprogram.patch
+++ /dev/null
@@ -1,16 +0,0 @@
1 policy.h | 2 +-
2 1 file changed, 1 insertion(+), 1 deletion(-)
3
4diff --git a/policy.h b/policy.h
5index 5afe34b..8e92c8b 100644
6--- a/policy.h
7+++ b/policy.h
8@@ -240,7 +240,7 @@
9 the sendmail choice below. Otherwise, select one of the other
10 choices as appropriate. */
11 #if 1
12-#define MAIL_PROGRAM "/usr/lib/sendmail -t"
13+#define MAIL_PROGRAM "${config.security.wrapperDir}/sendmail -t"
14 /* #define MAIL_PROGRAM "/usr/sbin/sendmail -t" */
15 #define MAIL_PROGRAM_TO_BODY 1
16 #define MAIL_PROGRAM_SUBJECT_BODY 1
diff --git a/overlays/waybar-systemd-inhibit/.envrc b/overlays/waybar-systemd-inhibit/.envrc
new file mode 100644
index 00000000..2c909235
--- /dev/null
+++ b/overlays/waybar-systemd-inhibit/.envrc
@@ -0,0 +1,4 @@
1use flake
2
3[[ -d ".venv" ]] || ( uv venv && uv sync )
4. .venv/bin/activate
diff --git a/overlays/waybar-systemd-inhibit/.gitignore b/overlays/waybar-systemd-inhibit/.gitignore
new file mode 100644
index 00000000..4ccfae70
--- /dev/null
+++ b/overlays/waybar-systemd-inhibit/.gitignore
@@ -0,0 +1,2 @@
1.venv
2**/__pycache__
diff --git a/overlays/waybar-systemd-inhibit/default.nix b/overlays/waybar-systemd-inhibit/default.nix
new file mode 100644
index 00000000..ae6b8c75
--- /dev/null
+++ b/overlays/waybar-systemd-inhibit/default.nix
@@ -0,0 +1,20 @@
1{ prev, final, flake, flakeInputs, ... }:
2
3let
4 workspace = flakeInputs.uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./.; };
5 pythonSet = flake.lib.pythonSet {
6 pkgs = final;
7 python = final.python312;
8 overlay = workspace.mkPyprojectOverlay {
9 sourcePreference = "wheel";
10 };
11 };
12 virtualEnv = pythonSet.mkVirtualEnv "waybar-systemd-inhibit-env" workspace.deps.default;
13in {
14 waybar-systemd-inhibit = virtualEnv.overrideAttrs (oldAttrs: {
15 meta = (oldAttrs.meta or {}) // {
16 mainProgram = "waybar-systemd-inhibit";
17 };
18 nativeBuildInputs = (oldAttrs.nativeBuildInputs or []) ++ [ final.gobject-introspection final.wrapGAppsHook ];
19 });
20}
diff --git a/overlays/waybar-systemd-inhibit/pyproject.toml b/overlays/waybar-systemd-inhibit/pyproject.toml
new file mode 100644
index 00000000..6c6240b8
--- /dev/null
+++ b/overlays/waybar-systemd-inhibit/pyproject.toml
@@ -0,0 +1,17 @@
1[project]
2name = "waybar-systemd-inhibit"
3version = "0.1.0"
4requires-python = ">=3.12"
5dependencies = [
6 "asyncclick>=8.1.8",
7 "asyncio>=3.4.3",
8 "dbus-next>=0.2.3",
9]
10
11[project.scripts]
12waybar-systemd-inhibit = "waybar_systemd_inhibit.__main__:main"
13waybar-systemd-inhibit-toggle = "waybar_systemd_inhibit.__main__:toggle"
14
15[build-system]
16requires = ["hatchling"]
17build-backend = "hatchling.build"
diff --git a/overlays/waybar-systemd-inhibit/uv.lock b/overlays/waybar-systemd-inhibit/uv.lock
new file mode 100644
index 00000000..4e10d145
--- /dev/null
+++ b/overlays/waybar-systemd-inhibit/uv.lock
@@ -0,0 +1,102 @@
1version = 1
2revision = 2
3requires-python = ">=3.12"
4
5[[package]]
6name = "anyio"
7version = "4.9.0"
8source = { registry = "https://pypi.org/simple" }
9dependencies = [
10 { name = "idna" },
11 { name = "sniffio" },
12 { name = "typing-extensions", marker = "python_full_version < '3.13'" },
13]
14sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" }
15wheels = [
16 { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" },
17]
18
19[[package]]
20name = "asyncclick"
21version = "8.1.8"
22source = { registry = "https://pypi.org/simple" }
23dependencies = [
24 { name = "anyio" },
25 { name = "colorama", marker = "sys_platform == 'win32'" },
26]
27sdist = { url = "https://files.pythonhosted.org/packages/cb/b5/e1e5fdf1c1bb7e6e614987c120a98d9324bf8edfaa5f5cd16a6235c9d91b/asyncclick-8.1.8.tar.gz", hash = "sha256:0f0eb0f280e04919d67cf71b9fcdfb4db2d9ff7203669c40284485c149578e4c", size = 232900, upload-time = "2025-01-06T09:46:52.694Z" }
28wheels = [
29 { url = "https://files.pythonhosted.org/packages/14/cc/a436f0fc2d04e57a0697e0f87a03b9eaed03ad043d2d5f887f8eebcec95f/asyncclick-8.1.8-py3-none-any.whl", hash = "sha256:eb1ccb44bc767f8f0695d592c7806fdf5bd575605b4ee246ffd5fadbcfdbd7c6", size = 99093, upload-time = "2025-01-06T09:46:51.046Z" },
30 { url = "https://files.pythonhosted.org/packages/92/c4/ae9e9d25522c6dc96ff167903880a0fe94d7bd31ed999198ee5017d977ed/asyncclick-8.1.8.0-py3-none-any.whl", hash = "sha256:be146a2d8075d4fe372ff4e877f23c8b5af269d16705c1948123b9415f6fd678", size = 99115, upload-time = "2025-01-06T09:50:52.72Z" },
31]
32
33[[package]]
34name = "asyncio"
35version = "3.4.3"
36source = { registry = "https://pypi.org/simple" }
37sdist = { url = "https://files.pythonhosted.org/packages/da/54/054bafaf2c0fb8473d423743e191fcdf49b2c1fd5e9af3524efbe097bafd/asyncio-3.4.3.tar.gz", hash = "sha256:83360ff8bc97980e4ff25c964c7bd3923d333d177aa4f7fb736b019f26c7cb41", size = 204411, upload-time = "2015-03-10T14:11:26.494Z" }
38wheels = [
39 { url = "https://files.pythonhosted.org/packages/22/74/07679c5b9f98a7cb0fc147b1ef1cc1853bc07a4eb9cb5731e24732c5f773/asyncio-3.4.3-py3-none-any.whl", hash = "sha256:c4d18b22701821de07bd6aea8b53d21449ec0ec5680645e5317062ea21817d2d", size = 101767, upload-time = "2015-03-10T14:05:10.959Z" },
40]
41
42[[package]]
43name = "colorama"
44version = "0.4.6"
45source = { registry = "https://pypi.org/simple" }
46sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
47wheels = [
48 { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
49]
50
51[[package]]
52name = "dbus-next"
53version = "0.2.3"
54source = { registry = "https://pypi.org/simple" }
55sdist = { url = "https://files.pythonhosted.org/packages/ce/45/6a40fbe886d60a8c26f480e7d12535502b5ba123814b3b9a0b002ebca198/dbus_next-0.2.3.tar.gz", hash = "sha256:f4eae26909332ada528c0a3549dda8d4f088f9b365153952a408e28023a626a5", size = 71112, upload-time = "2021-07-25T22:11:28.398Z" }
56wheels = [
57 { url = "https://files.pythonhosted.org/packages/d2/fc/c0a3f4c4eaa5a22fbef91713474666e13d0ea2a69c84532579490a9f2cc8/dbus_next-0.2.3-py3-none-any.whl", hash = "sha256:58948f9aff9db08316734c0be2a120f6dc502124d9642f55e90ac82ffb16a18b", size = 57885, upload-time = "2021-07-25T22:11:25.466Z" },
58]
59
60[[package]]
61name = "idna"
62version = "3.10"
63source = { registry = "https://pypi.org/simple" }
64sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
65wheels = [
66 { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
67]
68
69[[package]]
70name = "sniffio"
71version = "1.3.1"
72source = { registry = "https://pypi.org/simple" }
73sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
74wheels = [
75 { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
76]
77
78[[package]]
79name = "typing-extensions"
80version = "4.13.2"
81source = { registry = "https://pypi.org/simple" }
82sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" }
83wheels = [
84 { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" },
85]
86
87[[package]]
88name = "waybar-systemd-inhibit"
89version = "0.1.0"
90source = { editable = "." }
91dependencies = [
92 { name = "asyncclick" },
93 { name = "asyncio" },
94 { name = "dbus-next" },
95]
96
97[package.metadata]
98requires-dist = [
99 { name = "asyncclick", specifier = ">=8.1.8" },
100 { name = "asyncio", specifier = ">=3.4.3" },
101 { name = "dbus-next", specifier = ">=0.2.3" },
102]
diff --git a/overlays/waybar-systemd-inhibit/waybar_systemd_inhibit/__init__.py b/overlays/waybar-systemd-inhibit/waybar_systemd_inhibit/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/overlays/waybar-systemd-inhibit/waybar_systemd_inhibit/__init__.py
diff --git a/overlays/waybar-systemd-inhibit/waybar_systemd_inhibit/__main__.py b/overlays/waybar-systemd-inhibit/waybar_systemd_inhibit/__main__.py
new file mode 100644
index 00000000..35cc7fd1
--- /dev/null
+++ b/overlays/waybar-systemd-inhibit/waybar_systemd_inhibit/__main__.py
@@ -0,0 +1,117 @@
1import asyncclick as click
2from dbus_next.aio import MessageBus
3from dbus_next import BusType, Message, PropertyAccess
4import asyncio
5from functools import update_wrapper
6from dbus_next.service import ServiceInterface, method, dbus_property
7from dbus_next import Variant, DBusError
8import os
9import json
10
11class BlockInterface(ServiceInterface):
12 def __init__(self, system_bus, logind):
13 super().__init__('li.yggdrasil.WaybarSystemdInhibit')
14 self.system_bus = system_bus
15 self.logind = logind
16 self.fd = None
17
18 def Release(self):
19 if not self.fd:
20 return
21
22 os.close(self.fd)
23 self.fd = None
24 self.emit_properties_changed({'IsAcquired': False})
25
26 async def Acquire(self):
27 if self.fd:
28 return
29
30 res = await self.system_bus.call(Message(
31 destination='org.freedesktop.login1',
32 path='/org/freedesktop/login1',
33 interface='org.freedesktop.login1.Manager',
34 member='Inhibit',
35 signature='ssss',
36 body=[
37 "handle-lid-switch",
38 "waybar-systemd-inhibit",
39 "User request",
40 "block",
41 ],
42 ))
43 self.fd = res.unix_fds[res.body[0]]
44 self.emit_properties_changed({'IsAcquired': True})
45
46 @method()
47 async def ToggleBlock(self):
48 if self.fd:
49 self.Release()
50 else:
51 await self.Acquire()
52
53 @dbus_property(access=PropertyAccess.READ)
54 def IsAcquired(self) -> 'b':
55 return self.fd is not None
56
57
58@click.command()
59async def main():
60 system_bus = await MessageBus(bus_type=BusType.SYSTEM, negotiate_unix_fd=True).connect()
61 session_bus = await MessageBus(bus_type=BusType.SESSION).connect()
62
63 introspection = await system_bus.introspect('org.freedesktop.login1', '/org/freedesktop/login1')
64 obj = system_bus.get_proxy_object('org.freedesktop.login1', '/org/freedesktop/login1', introspection)
65 logind = obj.get_interface('org.freedesktop.login1.Manager')
66 properties = obj.get_interface('org.freedesktop.DBus.Properties')
67
68 def is_blocked_logind(what: str):
69 return "handle-lid-switch" in what.split(':')
70
71 def print_state(is_blocked: bool, is_acquired: bool = False):
72 icon = "&#xf0322;" if is_blocked else "&#xf06e7;"
73 text = f"<span font=\"Symbols Nerd Font Mono\">{icon}</span>"
74 if is_acquired:
75 text = f"<span color=\"#f28a21\">{text}</span>"
76 elif is_blocked:
77 text = f"<span color=\"#ffffff\">{text}</span>"
78 print(json.dumps({'text': text, 'tooltip': ("Manually inhibited" if is_acquired else None)}, separators=(',', ':')), flush=True)
79
80 print_state(is_blocked_logind(await logind.get_block_inhibited()))
81
82 async def get_inhibit():
83 introspection = await session_bus.introspect('li.yggdrasil.WaybarSystemdInhibit', '/li/yggdrasil/WaybarSystemdInhibit')
84 return session_bus.get_proxy_object('li.yggdrasil.WaybarSystemdInhibit', '/li/yggdrasil/WaybarSystemdInhibit', introspection)
85
86 async def on_logind_properties_changed(interface_name, changed_properties, invalidated_properties):
87 if 'BlockInhibited' not in changed_properties:
88 return
89
90 properties = (await get_inhibit()).get_interface('li.yggdrasil.WaybarSystemdInhibit')
91
92 print_state(is_blocked_logind(changed_properties['BlockInhibited'].value), await properties.get_is_acquired())
93
94 properties.on_properties_changed(on_logind_properties_changed)
95
96 session_bus.export('/li/yggdrasil/WaybarSystemdInhibit', BlockInterface(system_bus, logind))
97 await session_bus.request_name('li.yggdrasil.WaybarSystemdInhibit')
98
99 properties = (await get_inhibit()).get_interface('org.freedesktop.DBus.Properties')
100
101 async def on_inhibit_properties_changed(interface_name, changed_properties, invalidated_properties):
102 if 'IsAcquired' not in changed_properties:
103 return
104
105 print_state(is_blocked_logind(await logind.get_block_inhibited()), changed_properties['IsAcquired'].value)
106
107 properties.on_properties_changed(on_inhibit_properties_changed)
108
109 await session_bus.wait_for_disconnect()
110
111@click.command()
112async def toggle():
113 session_bus = await MessageBus(bus_type=BusType.SESSION).connect()
114 introspection = await session_bus.introspect('li.yggdrasil.WaybarSystemdInhibit', '/li/yggdrasil/WaybarSystemdInhibit')
115 obj = session_bus.get_proxy_object('li.yggdrasil.WaybarSystemdInhibit', '/li/yggdrasil/WaybarSystemdInhibit', introspection)
116 interface = obj.get_interface('li.yggdrasil.WaybarSystemdInhibit')
117 await interface.call_toggle_block()
diff --git a/overlays/waybar.nix b/overlays/waybar.nix
index 20f37255..e7e3b807 100644
--- a/overlays/waybar.nix
+++ b/overlays/waybar.nix
@@ -1,3 +1,8 @@
1{ final, prev, flakeInputs, ... }: 1{ final, prev, flakeInputs, ... }: prev.lib.composeExtensions
2 2 flakeInputs.waybar.overlays.default
3flakeInputs.waybar.overlays.default final prev 3 (final: prev: {
4 waybar = prev.waybar.overrideAttrs (oldAttrs: {
5 dontVersionCheck = true;
6 });
7 })
8 final prev
diff --git a/overlays/worktime/.envrc b/overlays/worktime/.envrc
new file mode 100644
index 00000000..2c909235
--- /dev/null
+++ b/overlays/worktime/.envrc
@@ -0,0 +1,4 @@
1use flake
2
3[[ -d ".venv" ]] || ( uv venv && uv sync )
4. .venv/bin/activate
diff --git a/overlays/worktime/.gitignore b/overlays/worktime/.gitignore
new file mode 100644
index 00000000..4ccfae70
--- /dev/null
+++ b/overlays/worktime/.gitignore
@@ -0,0 +1,2 @@
1.venv
2**/__pycache__
diff --git a/overlays/worktime/default.nix b/overlays/worktime/default.nix
index 5ecdf149..579cf7ad 100644
--- a/overlays/worktime/default.nix
+++ b/overlays/worktime/default.nix
@@ -1,13 +1,19 @@
1{ prev, ... }: 1{ prev, final, flake, flakeInputs, ... }:
2 2
3with prev.poetry2nix; 3let
4 4 workspace = flakeInputs.uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./.; };
5{ 5 pythonSet = flake.lib.pythonSet {
6 worktime = mkPoetryApplication { 6 pkgs = final;
7 python = prev.python310; 7 python = final.python312;
8 8 overlay = workspace.mkPyprojectOverlay {
9 projectDir = cleanPythonSources { src = ./.; }; 9 sourcePreference = "wheel";
10 10 };
11 meta.mainProgram = "worktime";
12 }; 11 };
12 virtualEnv = pythonSet.mkVirtualEnv "worktime" workspace.deps.default;
13in {
14 worktime = virtualEnv.overrideAttrs (oldAttrs: {
15 meta = (oldAttrs.meta or {}) // {
16 mainProgram = "worktime";
17 };
18 });
13} 19}
diff --git a/overlays/worktime/poetry.lock b/overlays/worktime/poetry.lock
deleted file mode 100644
index 54182b09..00000000
--- a/overlays/worktime/poetry.lock
+++ /dev/null
@@ -1,248 +0,0 @@
1# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
2
3[[package]]
4name = "backoff"
5version = "2.2.1"
6description = "Function decoration for backoff and retry"
7optional = false
8python-versions = ">=3.7,<4.0"
9files = [
10 {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"},
11 {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"},
12]
13
14[[package]]
15name = "certifi"
16version = "2022.12.7"
17description = "Python package for providing Mozilla's CA Bundle."
18optional = false
19python-versions = ">=3.6"
20files = [
21 {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"},
22 {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"},
23]
24
25[[package]]
26name = "charset-normalizer"
27version = "3.1.0"
28description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
29optional = false
30python-versions = ">=3.7.0"
31files = [
32 {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"},
33 {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"},
34 {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"},
35 {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"},
36 {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"},
37 {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"},
38 {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"},
39 {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"},
40 {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"},
41 {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"},
42 {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"},
43 {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"},
44 {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"},
45 {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"},
46 {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"},
47 {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"},
48 {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"},
49 {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"},
50 {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"},
51 {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"},
52 {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"},
53 {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"},
54 {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"},
55 {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"},
56 {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"},
57 {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"},
58 {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"},
59 {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"},
60 {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"},
61 {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"},
62 {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"},
63 {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"},
64 {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"},
65 {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"},
66 {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"},
67 {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"},
68 {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"},
69 {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"},
70 {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"},
71 {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"},
72 {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"},
73 {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"},
74 {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"},
75 {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"},
76 {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"},
77 {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"},
78 {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"},
79 {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"},
80 {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"},
81 {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"},
82 {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"},
83 {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"},
84 {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"},
85 {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"},
86 {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"},
87 {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"},
88 {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"},
89 {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"},
90 {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"},
91 {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"},
92 {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"},
93 {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"},
94 {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"},
95 {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"},
96 {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"},
97 {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"},
98 {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"},
99 {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"},
100 {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"},
101 {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"},
102 {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"},
103 {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"},
104 {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"},
105 {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"},
106 {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"},
107]
108
109[[package]]
110name = "idna"
111version = "3.4"
112description = "Internationalized Domain Names in Applications (IDNA)"
113optional = false
114python-versions = ">=3.5"
115files = [
116 {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
117 {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
118]
119
120[[package]]
121name = "jsonpickle"
122version = "3.0.2"
123description = "Python library for serializing any arbitrary object graph into JSON"
124optional = false
125python-versions = ">=3.7"
126files = [
127 {file = "jsonpickle-3.0.2-py3-none-any.whl", hash = "sha256:4a8442d97ca3f77978afa58068768dba7bff2dbabe79a9647bc3cdafd4ef019f"},
128 {file = "jsonpickle-3.0.2.tar.gz", hash = "sha256:e37abba4bfb3ca4a4647d28bb9f4706436f7b46c8a8333b4a718abafa8e46b37"},
129]
130
131[package.extras]
132docs = ["jaraco.packaging (>=3.2)", "rst.linker (>=1.9)", "sphinx"]
133testing = ["ecdsa", "feedparser", "gmpy2", "numpy", "pandas", "pymongo", "pytest (>=3.5,!=3.7.3)", "pytest-black-multipy", "pytest-checkdocs (>=1.2.3)", "pytest-cov", "pytest-flake8 (>=1.1.1)", "scikit-learn", "sqlalchemy"]
134testing-libs = ["simplejson", "ujson"]
135
136[[package]]
137name = "python-dateutil"
138version = "2.8.2"
139description = "Extensions to the standard Python datetime module"
140optional = false
141python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
142files = [
143 {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
144 {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
145]
146
147[package.dependencies]
148six = ">=1.5"
149
150[[package]]
151name = "pyxdg"
152version = "0.28"
153description = "PyXDG contains implementations of freedesktop.org standards in python."
154optional = false
155python-versions = "*"
156files = [
157 {file = "pyxdg-0.28-py2.py3-none-any.whl", hash = "sha256:bdaf595999a0178ecea4052b7f4195569c1ff4d344567bccdc12dfdf02d545ab"},
158 {file = "pyxdg-0.28.tar.gz", hash = "sha256:3267bb3074e934df202af2ee0868575484108581e6f3cb006af1da35395e88b4"},
159]
160
161[[package]]
162name = "requests"
163version = "2.28.2"
164description = "Python HTTP for Humans."
165optional = false
166python-versions = ">=3.7, <4"
167files = [
168 {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"},
169 {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"},
170]
171
172[package.dependencies]
173certifi = ">=2017.4.17"
174charset-normalizer = ">=2,<4"
175idna = ">=2.5,<4"
176urllib3 = ">=1.21.1,<1.27"
177
178[package.extras]
179socks = ["PySocks (>=1.5.6,!=1.5.7)"]
180use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
181
182[[package]]
183name = "six"
184version = "1.16.0"
185description = "Python 2 and 3 compatibility utilities"
186optional = false
187python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
188files = [
189 {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
190 {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
191]
192
193[[package]]
194name = "tabulate"
195version = "0.9.0"
196description = "Pretty-print tabular data"
197optional = false
198python-versions = ">=3.7"
199files = [
200 {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"},
201 {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"},
202]
203
204[package.extras]
205widechars = ["wcwidth"]
206
207[[package]]
208name = "toml"
209version = "0.10.2"
210description = "Python Library for Tom's Obvious, Minimal Language"
211optional = false
212python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
213files = [
214 {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
215 {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
216]
217
218[[package]]
219name = "uritools"
220version = "4.0.1"
221description = "URI parsing, classification and composition"
222optional = false
223python-versions = "~=3.7"
224files = [
225 {file = "uritools-4.0.1-py3-none-any.whl", hash = "sha256:d122d394ed6e6e15ac0fddba6a5b19e9fa204e7797507815cbfb0e1455ac0475"},
226 {file = "uritools-4.0.1.tar.gz", hash = "sha256:efc5c3a6de05404850685a8d3f34da8476b56aa3516fbf8eff5c8704c7a2826f"},
227]
228
229[[package]]
230name = "urllib3"
231version = "1.26.15"
232description = "HTTP library with thread-safe connection pooling, file post, and more."
233optional = false
234python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
235files = [
236 {file = "urllib3-1.26.15-py2.py3-none-any.whl", hash = "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42"},
237 {file = "urllib3-1.26.15.tar.gz", hash = "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305"},
238]
239
240[package.extras]
241brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
242secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
243socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
244
245[metadata]
246lock-version = "2.0"
247python-versions = "^3.10"
248content-hash = "d9137b4f8e37bba934abf732e4a2aeeb9924c4b6576830d8ae08bdb43b4e147f"
diff --git a/overlays/worktime/pyproject.toml b/overlays/worktime/pyproject.toml
index 08002d4d..42da51f5 100644
--- a/overlays/worktime/pyproject.toml
+++ b/overlays/worktime/pyproject.toml
@@ -1,23 +1,28 @@
1[tool.poetry] 1[project]
2name = "worktime" 2name = "worktime"
3version = "0.1.0" 3version = "1.0.0"
4description = "" 4requires-python = "~=3.12"
5authors = ["Gregor Kleen <gkleen@yggdrasil.li>"] 5dependencies = [
6 "pyxdg>=0.28,<0.29",
7 "python-dateutil>=2.9.0.post0,<3",
8 "uritools>=4.0.3,<5",
9 "requests>=2.32.3,<3",
10 "tabulate>=0.9.0,<0.10",
11 "toml>=0.10.2,<0.11",
12 "jsonpickle>=4.0.5,<5",
13 "frozendict>=2.4.6",
14 "atomicwriter>=0.2.5",
15 "desktop-notify>=1.3.3",
16]
6 17
7[tool.poetry.dependencies] 18[project.scripts]
8python = "^3.10"
9pyxdg = "^0.28"
10python-dateutil = "^2.8.2"
11uritools = "^4.0.1"
12requests = "^2.28.2"
13tabulate = "^0.9.0"
14backoff = "^2.2.1"
15toml = "^0.10.2"
16jsonpickle = "^3.0.2"
17
18[tool.poetry.scripts]
19worktime = "worktime.__main__:main" 19worktime = "worktime.__main__:main"
20worktime-ui = "worktime.__main__:ui"
21worktime-stop = "worktime.__main__:stop"
20 22
21[build-system] 23[build-system]
22requires = ["poetry-core"] 24requires = ["hatchling"]
23build-backend = "poetry.core.masonry.api" \ No newline at end of file 25build-backend = "hatchling.build"
26
27[dependency-groups]
28dev = []
diff --git a/overlays/worktime/uv.lock b/overlays/worktime/uv.lock
new file mode 100644
index 00000000..39de4ccf
--- /dev/null
+++ b/overlays/worktime/uv.lock
@@ -0,0 +1,248 @@
1version = 1
2revision = 2
3requires-python = ">=3.12, <4"
4
5[[package]]
6name = "atomicwriter"
7version = "0.2.5"
8source = { registry = "https://pypi.org/simple" }
9sdist = { url = "https://files.pythonhosted.org/packages/50/b4/dd04e186eb244d1ed84b1d0ebfba19ddc7f8886b98e345aaca4208b031d2/atomicwriter-0.2.5.tar.gz", hash = "sha256:5ced6afb0579377a13e191b17a16115e14c30ec00e6c38b60403f58235a867af", size = 64990, upload-time = "2025-05-24T20:35:42.538Z" }
10wheels = [
11 { url = "https://files.pythonhosted.org/packages/99/7c/672a0de09b0b355a2ffa521ef25cf106f1984823379dee37f7305fdc1774/atomicwriter-0.2.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c1fab874e62ebe96f1af0e965dc1e92c4c1ef2e2e9612a444371b8fc751ec43", size = 234141, upload-time = "2025-05-24T20:34:32.74Z" },
12 { url = "https://files.pythonhosted.org/packages/b9/0c/e1c5bad033284c212c0a77121b48dd4147f80e9a7cd82a9d2ce0a2160901/atomicwriter-0.2.5-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:8dbb67cc730be7d6bdfd5e991271bc17052be8fb2e4fa27854b47d8a76d36349", size = 245788, upload-time = "2025-05-24T20:34:33.897Z" },
13 { url = "https://files.pythonhosted.org/packages/f4/d3/7036e203cc5fc4c49bf916b4ba158e0d2779de127afad5963edd7e3b9400/atomicwriter-0.2.5-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:a4e7f81932839c738425dc96ad98e4a7511b740cd3d75f480bfabbcf8e6f7eae", size = 260428, upload-time = "2025-05-24T20:34:35.533Z" },
14 { url = "https://files.pythonhosted.org/packages/e5/b9/9a4d235a8d67fb442302dc0f3ea2394b7bd994bfc99b1dc0f744c7852418/atomicwriter-0.2.5-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:de37a3a5d1b57b719cfb0b81a11cab2114acfdc2c36051bf0af72d05eb644411", size = 263648, upload-time = "2025-05-24T20:34:36.72Z" },
15 { url = "https://files.pythonhosted.org/packages/71/7c/32d4ddad53375de42f3e972bb0633ec76f2c31772f2e508479d4788651d9/atomicwriter-0.2.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b925e55750092fd482565b6068b8c8366fd79de526681af9e58eb209f0deeca", size = 323775, upload-time = "2025-05-24T20:34:37.968Z" },
16 { url = "https://files.pythonhosted.org/packages/06/fe/6a226368a3f7ea30001fbd165f6a97f28c8f1a884896357b3d694983f5d2/atomicwriter-0.2.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:538f78f25e01584535782397211c66b8b3c9de90c2d1fc01a668ddce73dd0cb2", size = 340819, upload-time = "2025-05-24T20:34:39.63Z" },
17 { url = "https://files.pythonhosted.org/packages/92/95/b035b2296c483fde5392c629e0b6e3844eba6e54ea965c4b8827379b0893/atomicwriter-0.2.5-cp312-cp312-win_amd64.whl", hash = "sha256:1d2d49a1b94ea7b289be9f7134d756bfb0bbf53eb0e58411334ed1b9958abe5e", size = 152789, upload-time = "2025-05-24T20:34:40.905Z" },
18 { url = "https://files.pythonhosted.org/packages/da/25/caa0959ae8ce24763e24e1f45be6cb897414545d224a155f929d496d6812/atomicwriter-0.2.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f5490fd5bec378509521f7c2a19a64031a0de07d368d76733c3f76a0b9f026b", size = 233830, upload-time = "2025-05-24T20:34:42.532Z" },
19 { url = "https://files.pythonhosted.org/packages/d2/76/3c41bfd4fd74bc63bec29f05a806a767258eea7cf151496b4ab015cb5323/atomicwriter-0.2.5-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:a4dada83ff1255c7e640363cc2a4399ab9a822d4dbc9c18f55bbf0c8b12ce056", size = 245461, upload-time = "2025-05-24T20:34:44.454Z" },
20 { url = "https://files.pythonhosted.org/packages/c3/1e/5512dbdfdc3f4ab12f5923c50ae4765cc2fc65a9f112bb9dccbcbe60b395/atomicwriter-0.2.5-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:ef2cf15e67513f05ad37d4cec48e403982c6b3c07f491472effd76d2157de7e2", size = 259892, upload-time = "2025-05-24T20:34:45.688Z" },
21 { url = "https://files.pythonhosted.org/packages/e5/1d/2382b6cacb119115828eb519697a555900bcfdb062efeb0f82603295402d/atomicwriter-0.2.5-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:73618f74c3c5f5401d3da0a3cd3043f23de5b6bb4a3d85bc580940a441355d25", size = 263125, upload-time = "2025-05-24T20:34:47.205Z" },
22 { url = "https://files.pythonhosted.org/packages/07/d7/c4d68386161870db4a8d0452f0655a19902fa435b749c12e6ef800e89b19/atomicwriter-0.2.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbd5eda80710ddac7aefb421c79cef6b905852a827e764f0f12fcbaa88919f7a", size = 323503, upload-time = "2025-05-24T20:34:48.417Z" },
23 { url = "https://files.pythonhosted.org/packages/b7/08/0fc03c0736ab8466e1b47a3ee17a528da18019cff93b7c4c2b33df82c19e/atomicwriter-0.2.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4776aaca40bc3040c3716c2adad74625c42285083ff31e8bf24a95315225c7b", size = 340156, upload-time = "2025-05-24T20:34:50.389Z" },
24 { url = "https://files.pythonhosted.org/packages/fa/09/7ba888cf4d90bcabd9e82db3bdb9de50e4ef072e0ea0d375cd1931b79349/atomicwriter-0.2.5-cp313-cp313-win_amd64.whl", hash = "sha256:225ed1fbfa1996d9b0b2252f8a5d81263e51cbc797086d830f488c35b1d2ab42", size = 152274, upload-time = "2025-05-24T20:34:51.785Z" },
25 { url = "https://files.pythonhosted.org/packages/2a/70/07d2ba2e0a126cfecfbfed46baf599c9e2155f4c8338fed4d3ae0041b133/atomicwriter-0.2.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:63b55982cfa47232f179689933bf003eefb2bd33464235883ed3ce7322cf38f3", size = 232879, upload-time = "2025-05-24T20:34:53.195Z" },
26 { url = "https://files.pythonhosted.org/packages/f6/4d/397eb5435917135df93b339d849884bb1125896b1e15163c5244aa590336/atomicwriter-0.2.5-cp313-cp313t-macosx_11_0_x86_64.whl", hash = "sha256:e33f40b2a27f8831beeabb485923acb6dd067cc70bba1a63096749b3dc4747ff", size = 244386, upload-time = "2025-05-24T20:34:54.852Z" },
27 { url = "https://files.pythonhosted.org/packages/8b/01/73f0b683fa55e61dd29d30e48e9a75ddb049e6dad0ac4ae1a29dbc05f21e/atomicwriter-0.2.5-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:c646e115e88147d71f845a005fc53910f22c4dc65bd634768cb90b7f34259359", size = 258255, upload-time = "2025-05-24T20:34:56.046Z" },
28 { url = "https://files.pythonhosted.org/packages/4b/19/692387c1fb1b8714a9b2fab99a58850fd4136bed988814c8ff74d0c8de02/atomicwriter-0.2.5-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:47f974e986ff6514351c3ea75041009a514be0c34c225c062b0ad8a28ec9c0a3", size = 261768, upload-time = "2025-05-24T20:34:57.795Z" },
29 { url = "https://files.pythonhosted.org/packages/3e/f2/4d466f52ee635cc54011713272f302584c6d1ce612c331d9989fa6fa672f/atomicwriter-0.2.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e1db8b9004cd3f628166e83b25eb814b82345f9d6bc15e99b6d201c355455b45", size = 321975, upload-time = "2025-05-24T20:34:59.45Z" },
30 { url = "https://files.pythonhosted.org/packages/84/ad/0189ad9783ca6609df47e06cc0cd22866a8073d46478f59c6ab3ec13e0fb/atomicwriter-0.2.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a7da4a114121ab865663578b801a0520b2b518d4591af0bd294f6aac0dad243b", size = 338946, upload-time = "2025-05-24T20:35:01.501Z" },
31 { url = "https://files.pythonhosted.org/packages/94/79/2c4d8f75eeb09192cf572957f031271998f3c985fabd79d513fff66ac715/atomicwriter-0.2.5-cp313-cp313t-win_amd64.whl", hash = "sha256:7aab4b3956cc17219e7e4da76e8a1bceb3d3aeaf03234f89b90e234a2adcf27b", size = 151571, upload-time = "2025-05-24T20:35:02.747Z" },
32 { url = "https://files.pythonhosted.org/packages/32/19/d6a686d189c3577e7f08b33df398b959c24bf74b3cec34359104db1a24ff/atomicwriter-0.2.5-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:8d0fccac2dfe5d884d97edbda28be9c16d55faee9bdf66f53a99384ac387cc43", size = 239320, upload-time = "2025-05-24T20:35:04.028Z" },
33 { url = "https://files.pythonhosted.org/packages/8e/35/35571a4eed57816c3b5fdbefcb15f38563fbe4f3a4a7d1588c8ef899afaf/atomicwriter-0.2.5-cp39-abi3-macosx_11_0_x86_64.whl", hash = "sha256:6583c24333508839db2156d895cbbb5cd3ff20d4f9c698e341435e5b35990eaa", size = 250818, upload-time = "2025-05-24T20:35:05.21Z" },
34 { url = "https://files.pythonhosted.org/packages/81/d9/145093630bc25f115a49d32d9ef66745f5cdef787492d77fd27e74d20389/atomicwriter-0.2.5-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:136a9902ae3f1c0cb262a07dd3ac85069d71f8b11347cd740030567e67d611aa", size = 265796, upload-time = "2025-05-24T20:35:06.388Z" },
35 { url = "https://files.pythonhosted.org/packages/58/32/d1881adade2ebc70aa9dbb61cadabc2c00cfa99a7a5d6ba48f44e279056f/atomicwriter-0.2.5-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0b6830434b6a49c19473c3f3975dfa0a87dec95bee81297f7393e378f9a0b82f", size = 269378, upload-time = "2025-05-24T20:35:07.578Z" },
36 { url = "https://files.pythonhosted.org/packages/93/f5/2661ea763784a4991c4c7be5c932a468937bd1d4618b833a63ec638a3b76/atomicwriter-0.2.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:53095a01891a2901aa04c10c8de52c0ba41e0d8a4a1893318cf34ccbdbde00b7", size = 328167, upload-time = "2025-05-24T20:35:08.764Z" },
37 { url = "https://files.pythonhosted.org/packages/ec/bc/e3aa521671a589bee9662d3e2108e4835a5d80e6da76e4d05d98d1c78005/atomicwriter-0.2.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ecf4dc3983bb1f28b21cb09c2d96b6936d8864c559dcf151b57813cb1eae998b", size = 347153, upload-time = "2025-05-24T20:35:10.507Z" },
38 { url = "https://files.pythonhosted.org/packages/59/b7/e190383e7240b1f247c6df9bc6667db8df10190cd0bb2dba8ea6bd704ea4/atomicwriter-0.2.5-cp39-abi3-win_amd64.whl", hash = "sha256:92cff264a20364301ab341b332fd0112866870b8cb35caf99a3f3fee0e6c19e8", size = 156374, upload-time = "2025-05-24T20:35:11.716Z" },
39]
40
41[[package]]
42name = "certifi"
43version = "2025.1.31"
44source = { registry = "https://pypi.org/simple" }
45sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577, upload-time = "2025-01-31T02:16:47.166Z" }
46wheels = [
47 { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393, upload-time = "2025-01-31T02:16:45.015Z" },
48]
49
50[[package]]
51name = "charset-normalizer"
52version = "3.4.1"
53source = { registry = "https://pypi.org/simple" }
54sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188, upload-time = "2024-12-24T18:12:35.43Z" }
55wheels = [
56 { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105, upload-time = "2024-12-24T18:10:38.83Z" },
57 { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404, upload-time = "2024-12-24T18:10:44.272Z" },
58 { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423, upload-time = "2024-12-24T18:10:45.492Z" },
59 { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184, upload-time = "2024-12-24T18:10:47.898Z" },
60 { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268, upload-time = "2024-12-24T18:10:50.589Z" },
61 { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601, upload-time = "2024-12-24T18:10:52.541Z" },
62 { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098, upload-time = "2024-12-24T18:10:53.789Z" },
63 { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520, upload-time = "2024-12-24T18:10:55.048Z" },
64 { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852, upload-time = "2024-12-24T18:10:57.647Z" },
65 { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488, upload-time = "2024-12-24T18:10:59.43Z" },
66 { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192, upload-time = "2024-12-24T18:11:00.676Z" },
67 { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550, upload-time = "2024-12-24T18:11:01.952Z" },
68 { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785, upload-time = "2024-12-24T18:11:03.142Z" },
69 { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698, upload-time = "2024-12-24T18:11:05.834Z" },
70 { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162, upload-time = "2024-12-24T18:11:07.064Z" },
71 { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263, upload-time = "2024-12-24T18:11:08.374Z" },
72 { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966, upload-time = "2024-12-24T18:11:09.831Z" },
73 { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992, upload-time = "2024-12-24T18:11:12.03Z" },
74 { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162, upload-time = "2024-12-24T18:11:13.372Z" },
75 { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972, upload-time = "2024-12-24T18:11:14.628Z" },
76 { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095, upload-time = "2024-12-24T18:11:17.672Z" },
77 { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668, upload-time = "2024-12-24T18:11:18.989Z" },
78 { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073, upload-time = "2024-12-24T18:11:21.507Z" },
79 { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732, upload-time = "2024-12-24T18:11:22.774Z" },
80 { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391, upload-time = "2024-12-24T18:11:24.139Z" },
81 { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702, upload-time = "2024-12-24T18:11:26.535Z" },
82 { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767, upload-time = "2024-12-24T18:12:32.852Z" },
83]
84
85[[package]]
86name = "dbus-next"
87version = "0.2.3"
88source = { registry = "https://pypi.org/simple" }
89sdist = { url = "https://files.pythonhosted.org/packages/ce/45/6a40fbe886d60a8c26f480e7d12535502b5ba123814b3b9a0b002ebca198/dbus_next-0.2.3.tar.gz", hash = "sha256:f4eae26909332ada528c0a3549dda8d4f088f9b365153952a408e28023a626a5", size = 71112, upload-time = "2021-07-25T22:11:28.398Z" }
90wheels = [
91 { url = "https://files.pythonhosted.org/packages/d2/fc/c0a3f4c4eaa5a22fbef91713474666e13d0ea2a69c84532579490a9f2cc8/dbus_next-0.2.3-py3-none-any.whl", hash = "sha256:58948f9aff9db08316734c0be2a120f6dc502124d9642f55e90ac82ffb16a18b", size = 57885, upload-time = "2021-07-25T22:11:25.466Z" },
92]
93
94[[package]]
95name = "desktop-notify"
96version = "1.3.3"
97source = { registry = "https://pypi.org/simple" }
98dependencies = [
99 { name = "dbus-next" },
100]
101sdist = { url = "https://files.pythonhosted.org/packages/7a/d8/7ae5779257f5f1aa0a2d50c02d70b29522bd414692f3d3bd18ef119fe82d/desktop-notify-1.3.3.tar.gz", hash = "sha256:62934ad1f72f292f9a3af5ffe45af32814af18c396c00369385540c72bf08077", size = 7828, upload-time = "2021-01-03T16:46:36.483Z" }
102wheels = [
103 { url = "https://files.pythonhosted.org/packages/0a/cd/a7e3bd0262f3e8a9272fd24d0193e24dad7cb4e4edd27da48e74b5523e59/desktop_notify-1.3.3-py3-none-any.whl", hash = "sha256:8ad7ecc3a9a603dd5fa3cdc11cc6265cfbc7f6df9d8ed240f4663f43ef0de37a", size = 9937, upload-time = "2021-01-03T16:46:35.157Z" },
104]
105
106[[package]]
107name = "frozendict"
108version = "2.4.6"
109source = { registry = "https://pypi.org/simple" }
110sdist = { url = "https://files.pythonhosted.org/packages/bb/59/19eb300ba28e7547538bdf603f1c6c34793240a90e1a7b61b65d8517e35e/frozendict-2.4.6.tar.gz", hash = "sha256:df7cd16470fbd26fc4969a208efadc46319334eb97def1ddf48919b351192b8e", size = 316416, upload-time = "2024-10-13T12:15:32.449Z" }
111wheels = [
112 { url = "https://files.pythonhosted.org/packages/04/13/d9839089b900fa7b479cce495d62110cddc4bd5630a04d8469916c0e79c5/frozendict-2.4.6-py311-none-any.whl", hash = "sha256:d065db6a44db2e2375c23eac816f1a022feb2fa98cbb50df44a9e83700accbea", size = 16148, upload-time = "2024-10-13T12:15:26.839Z" },
113 { url = "https://files.pythonhosted.org/packages/ba/d0/d482c39cee2ab2978a892558cf130681d4574ea208e162da8958b31e9250/frozendict-2.4.6-py312-none-any.whl", hash = "sha256:49344abe90fb75f0f9fdefe6d4ef6d4894e640fadab71f11009d52ad97f370b9", size = 16146, upload-time = "2024-10-13T12:15:28.16Z" },
114 { url = "https://files.pythonhosted.org/packages/a5/8e/b6bf6a0de482d7d7d7a2aaac8fdc4a4d0bb24a809f5ddd422aa7060eb3d2/frozendict-2.4.6-py313-none-any.whl", hash = "sha256:7134a2bb95d4a16556bb5f2b9736dceb6ea848fa5b6f3f6c2d6dba93b44b4757", size = 16146, upload-time = "2024-10-13T12:15:29.495Z" },
115]
116
117[[package]]
118name = "idna"
119version = "3.10"
120source = { registry = "https://pypi.org/simple" }
121sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
122wheels = [
123 { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
124]
125
126[[package]]
127name = "jsonpickle"
128version = "4.0.5"
129source = { registry = "https://pypi.org/simple" }
130sdist = { url = "https://files.pythonhosted.org/packages/d6/33/4bda317ab294722fcdfff8f63aab74af9fda3675a4652d984a101aa7587e/jsonpickle-4.0.5.tar.gz", hash = "sha256:f299818b39367c361b3f26bdba827d4249ab5d383cd93144d0f94b5417aacb35", size = 315661, upload-time = "2025-03-29T19:22:56.92Z" }
131wheels = [
132 { url = "https://files.pythonhosted.org/packages/dc/1b/0e79cf115e0f54f1e8f56effb6ffd2ef8f92e9c324d692ede660067f1bfe/jsonpickle-4.0.5-py3-none-any.whl", hash = "sha256:b4ac7d0a75ddcdfd93445737f1d36ff28768690d43e54bf5d0ddb1d915e580df", size = 46382, upload-time = "2025-03-29T19:22:54.252Z" },
133]
134
135[[package]]
136name = "python-dateutil"
137version = "2.9.0.post0"
138source = { registry = "https://pypi.org/simple" }
139dependencies = [
140 { name = "six" },
141]
142sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
143wheels = [
144 { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
145]
146
147[[package]]
148name = "pyxdg"
149version = "0.28"
150source = { registry = "https://pypi.org/simple" }
151sdist = { url = "https://files.pythonhosted.org/packages/b0/25/7998cd2dec731acbd438fbf91bc619603fc5188de0a9a17699a781840452/pyxdg-0.28.tar.gz", hash = "sha256:3267bb3074e934df202af2ee0868575484108581e6f3cb006af1da35395e88b4", size = 77776, upload-time = "2022-06-05T11:35:01Z" }
152wheels = [
153 { url = "https://files.pythonhosted.org/packages/e5/8d/cf41b66a8110670e3ad03dab9b759704eeed07fa96e90fdc0357b2ba70e2/pyxdg-0.28-py2.py3-none-any.whl", hash = "sha256:bdaf595999a0178ecea4052b7f4195569c1ff4d344567bccdc12dfdf02d545ab", size = 49520, upload-time = "2022-06-05T11:34:58.832Z" },
154]
155
156[[package]]
157name = "requests"
158version = "2.32.3"
159source = { registry = "https://pypi.org/simple" }
160dependencies = [
161 { name = "certifi" },
162 { name = "charset-normalizer" },
163 { name = "idna" },
164 { name = "urllib3" },
165]
166sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" }
167wheels = [
168 { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" },
169]
170
171[[package]]
172name = "six"
173version = "1.17.0"
174source = { registry = "https://pypi.org/simple" }
175sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
176wheels = [
177 { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
178]
179
180[[package]]
181name = "tabulate"
182version = "0.9.0"
183source = { registry = "https://pypi.org/simple" }
184sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" }
185wheels = [
186 { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" },
187]
188
189[[package]]
190name = "toml"
191version = "0.10.2"
192source = { registry = "https://pypi.org/simple" }
193sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" }
194wheels = [
195 { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" },
196]
197
198[[package]]
199name = "uritools"
200version = "4.0.3"
201source = { registry = "https://pypi.org/simple" }
202sdist = { url = "https://files.pythonhosted.org/packages/d3/43/4182fb2a03145e6d38698e38b49114ce59bc8c79063452eb585a58f8ce78/uritools-4.0.3.tar.gz", hash = "sha256:ee06a182a9c849464ce9d5fa917539aacc8edd2a4924d1b7aabeeecabcae3bc2", size = 24184, upload-time = "2024-05-28T18:07:45.194Z" }
203wheels = [
204 { url = "https://files.pythonhosted.org/packages/e6/17/5a4510d9ca9cc8be217ce359eb54e693dca81cf4d442308b282d5131b17d/uritools-4.0.3-py3-none-any.whl", hash = "sha256:bae297d090e69a0451130ffba6f2f1c9477244aa0a5543d66aed2d9f77d0dd9c", size = 10304, upload-time = "2024-05-28T18:07:42.731Z" },
205]
206
207[[package]]
208name = "urllib3"
209version = "2.3.0"
210source = { registry = "https://pypi.org/simple" }
211sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268, upload-time = "2024-12-22T07:47:30.032Z" }
212wheels = [
213 { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369, upload-time = "2024-12-22T07:47:28.074Z" },
214]
215
216[[package]]
217name = "worktime"
218version = "1.0.0"
219source = { editable = "." }
220dependencies = [
221 { name = "atomicwriter" },
222 { name = "desktop-notify" },
223 { name = "frozendict" },
224 { name = "jsonpickle" },
225 { name = "python-dateutil" },
226 { name = "pyxdg" },
227 { name = "requests" },
228 { name = "tabulate" },
229 { name = "toml" },
230 { name = "uritools" },
231]
232
233[package.metadata]
234requires-dist = [
235 { name = "atomicwriter", specifier = ">=0.2.5" },
236 { name = "desktop-notify", specifier = ">=1.3.3" },
237 { name = "frozendict", specifier = ">=2.4.6" },
238 { name = "jsonpickle", specifier = ">=4.0.5,<5" },
239 { name = "python-dateutil", specifier = ">=2.9.0.post0,<3" },
240 { name = "pyxdg", specifier = ">=0.28,<0.29" },
241 { name = "requests", specifier = ">=2.32.3,<3" },
242 { name = "tabulate", specifier = ">=0.9.0,<0.10" },
243 { name = "toml", specifier = ">=0.10.2,<0.11" },
244 { name = "uritools", specifier = ">=4.0.3,<5" },
245]
246
247[package.metadata.requires-dev]
248dev = []
diff --git a/overlays/worktime/worktime/__main__.py b/overlays/worktime/worktime/__main__.py
index 362c8da4..bf24bbec 100755
--- a/overlays/worktime/worktime/__main__.py
+++ b/overlays/worktime/worktime/__main__.py
@@ -1,10 +1,12 @@
1import requests 1import requests
2from requests.exceptions import HTTPError 2from requests.exceptions import HTTPError
3from requests.auth import HTTPBasicAuth 3from requests.auth import HTTPBasicAuth
4from requests.adapters import HTTPAdapter, Retry
4from datetime import * 5from datetime import *
5from xdg import BaseDirectory 6from xdg import BaseDirectory
6import toml 7import toml
7from uritools import (uricompose) 8from uritools import uricompose
9from urllib.parse import urljoin
8 10
9from inspect import signature 11from inspect import signature
10 12
@@ -23,80 +25,80 @@ import argparse
23from copy import deepcopy 25from copy import deepcopy
24 26
25import sys 27import sys
26from sys import stderr 28from sys import stderr, stdout
27 29
28from tabulate import tabulate 30from tabulate import tabulate
29 31
30from itertools import groupby, count 32from itertools import groupby, count, islice
31from functools import cache, partial 33from functools import cache, partial
32 34
33import backoff
34
35from pathlib import Path 35from pathlib import Path
36 36
37from collections import defaultdict 37from collections import defaultdict
38from collections.abc import Iterable, Generator
39from typing import Any
38 40
39import jsonpickle 41import jsonpickle
40from hashlib import blake2s 42from hashlib import blake2s
43import json
44
45import asyncio
46
47from frozendict import frozendict
48from contextlib import closing
49import os
50from time import clock_gettime_ns, CLOCK_MONOTONIC
51from atomicwriter import AtomicWriter
52import desktop_notify.aio as notify
53
54class BearerAuth(requests.auth.AuthBase):
55 def __init__(self, token):
56 self.token = token
57 def __call__(self, r):
58 r.headers["authorization"] = "Bearer " + self.token
59 return r
60
61class KimaiSession(requests.Session):
62 def __init__(self, base_url: str, api_token: str):
63 super().__init__()
64 self.base_url = base_url
65 self.auth = BearerAuth(api_token)
66 retries = Retry(total=5, backoff_factor=0.1, status_forcelist=[500, 502, 503, 504])
67 super().mount(base_url, HTTPAdapter(max_retries=retries))
68
69 def request(self, method, url, *args, **kwargs):
70 joined_url = urljoin(self.base_url, url)
71 return super().request(method, joined_url, *args, headers = {'Accept': 'application/json'} | (kwargs['headers'] if 'headers' in kwargs else {}), **{k: v for k, v in kwargs.items() if k not in ['headers']})
72
73class KimaiAPI(object):
74 def __init__(self, base_url: str, api_token: str, clients: Iterable[str]):
75 self._session = KimaiSession(base_url, api_token)
76 self._kimai_clients = self._session.get('/api/customers').json()
77 self._client_ids = self.resolve_clients(clients)
78 kimai_user = self._session.get('/api/users/me').json()
79 self._tz = gettz(kimai_user['timezone'])
80
81 def resolve_clients(self, clients: Iterable[str]) -> frozenset[int]:
82 return frozenset({ client['id'] for client in self._kimai_clients if client['name'] in clients })
83
84 def render_datetime(self, datetime: datetime) -> str:
85 return datetime.astimezone(self._tz).strftime('%Y-%m-%dT%H:%M:%S')
86
87 def get_timesheets(self, params: dict[str, Any] = {}) -> Generator[Any]:
88 for page in count(start=1):
89 resp = self._session.get('/api/timesheets', params=params | {'size': 100, 'page': page})
90 if resp.status_code == 404:
91 break
92 yield from resp.json()
41 93
42class TogglAPISection(Enum): 94 def entry_durations(self, start_date: datetime, *, end_date: datetime, clients: Iterable[str] | None = None) -> Generator[timedelta]:
43 TOGGL = '/api/v9' 95 client_ids = None
44 REPORTS = '/reports/api/v2' 96 if clients is not None and not clients:
45
46class TogglAPIError(Exception):
47 def __init__(self, response, *, http_error=None):
48 self.http_error = http_error
49 self.response = response
50
51 def __str__(self):
52 if not self.http_error is None:
53 return str(self.http_error)
54 else:
55 return self.response.text
56
57class TogglAPI(object):
58 def __init__(self, api_token, workspace_id, client_ids):
59 self._api_token = api_token
60 self._workspace_id = workspace_id
61 self._client_ids = set(map(int, client_ids.split(','))) if client_ids else None
62
63 def _make_url(self, api=TogglAPISection.TOGGL, section=['me', 'time_entries', 'current'], params={}):
64 if api is TogglAPISection.REPORTS:
65 params.update({'user_agent': 'worktime', 'workspace_id': self._workspace_id})
66
67 api_path = api.value
68 section_path = '/'.join(section)
69 uri = uricompose(scheme='https', host='api.track.toggl.com', path=f"{api_path}/{section_path}", query=params)
70
71 return uri
72
73 def _query(self, url, method):
74 response = self._raw_query(url, method)
75 response.raise_for_status()
76 return response
77
78 @backoff.on_predicate(
79 backoff.expo,
80 factor=0.1, max_value=2,
81 predicate=lambda r: r.status_code == 429,
82 max_time=10,
83 )
84 def _raw_query(self, url, method):
85 headers = {'content-type': 'application/json', 'accept': 'application/json'}
86 response = None
87
88 if method == 'GET':
89 response = requests.get(url, headers=headers, auth=HTTPBasicAuth(self._api_token, 'api_token'))
90 elif method == 'POST':
91 response = requests.post(url, headers=headers, auth=HTTPBasicAuth(self._api_token, 'api_token'))
92 else:
93 raise ValueError(f"Undefined HTTP method “{method}”")
94
95 return response
96
97 def entry_durations(self, start_date, *, end_date, rounding=False, client_ids):
98 if client_ids is not None and not client_ids:
99 return 97 return
98 elif clients is None:
99 client_ids = self._client_ids
100 else:
101 client_ids = self.resolve_clients(clients)
100 102
101 cache_dir = Path(BaseDirectory.save_cache_path('worktime')) / 'entry_durations' 103 cache_dir = Path(BaseDirectory.save_cache_path('worktime')) / 'entry_durations'
102 step = timedelta(days = 120) 104 step = timedelta(days = 120)
@@ -115,11 +117,8 @@ class TogglAPI(object):
115 cache_key = blake2s(jsonpickle.encode({ 117 cache_key = blake2s(jsonpickle.encode({
116 'start': req_start, 118 'start': req_start,
117 'end': req_end, 119 'end': req_end,
118 'rounding': rounding, 120 'client_ids': client_ids,
119 'clients': client_ids, 121 }).encode('utf-8'), key = self._session.auth.token.encode('utf-8')).hexdigest()
120 'workspace': self._workspace_id,
121 'workspace_clients': self._client_ids
122 }).encode('utf-8'), key = self._api_token.encode('utf-8')).hexdigest()
123 cache_path = cache_dir / cache_key[:2] / cache_key[2:4] / f'{cache_key[4:]}.json' 122 cache_path = cache_dir / cache_key[:2] / cache_key[2:4] / f'{cache_key[4:]}.json'
124 try: 123 try:
125 with cache_path.open('r', encoding='utf-8') as ch: 124 with cache_path.open('r', encoding='utf-8') as ch:
@@ -129,85 +128,83 @@ class TogglAPI(object):
129 pass 128 pass
130 129
131 entries = list() 130 entries = list()
132 params = { 'since': (req_start - timedelta(days=1)).date().isoformat(), 131 params = {
133 'until': (req_end + timedelta(days=1)).date().isoformat(), 132 'begin': self.render_datetime(req_start),
134 'rounding': 'yes' if rounding else 'no', 133 'end': self.render_datetime(req_end),
135 'billable': 'yes' 134 'customers[]': list(client_ids),
136 } 135 'billable': 1,
137 if client_ids is not None: 136 }
138 params |= { 'client_ids': ','.join(map(str, client_ids)) } 137
139 for page in count(start = 1): 138 for entry in self.get_timesheets(params):
140 url = self._make_url(api = TogglAPISection.REPORTS, section = ['details'], params = params | { 'page': page }) 139 if entry['end'] is None:
141 r = self._query(url = url, method='GET') 140 continue
142 if not r or not r.json(): 141
143 raise TogglAPIError(r) 142 start = isoparse(entry['begin'])
144 report = r.json() 143 end = isoparse(entry['end'])
145 for entry in report['data']: 144
146 start = isoparse(entry['start']) 145 if start > req_end or end < req_start:
147 end = isoparse(entry['end']) 146 continue
148
149 if start > req_end or end < req_start:
150 continue
151 147
152 x = min(end, req_end) - max(start, req_start) 148 x = min(end, req_end) - max(start, req_start)
153 if cache_key: 149 if cache_key:
154 entries.append(x) 150 entries.append(x)
155 yield x 151 yield x
156 if not report['data']:
157 break
158 152
159 if cache_path: 153 if cache_path:
160 cache_path.parent.mkdir(parents=True, exist_ok=True) 154 cache_path.parent.mkdir(parents=True, exist_ok=True)
161 with cache_path.open('w', encoding='utf-8') as ch: 155 with cache_path.open('w', encoding='utf-8') as ch:
162 ch.write(jsonpickle.encode(entries)) 156 ch.write(jsonpickle.encode(entries))
163 # res = timedelta(milliseconds=report['total_billable']) if report['total_billable'] else timedelta(milliseconds=0)
164 # return res
165 157
166 def get_billable_hours(self, start_date, end_date=datetime.now(timezone.utc), rounding=False): 158 def get_billable_hours(self, start_date: datetime, end_date: datetime = datetime.now(timezone.utc)) -> timedelta:
167 billable_acc = timedelta(milliseconds = 0) 159 return sum(self.entry_durations(start_date, end_date=end_date), start=timedelta(milliseconds=0))
168 if 0 in self._client_ids:
169 url = self._make_url(api = TogglAPISection.TOGGL, section = ['workspaces', self._workspace_id, 'clients'])
170 r = self._query(url = url, method = 'GET')
171 if not r or not r.json():
172 raise TogglAPIError(r)
173 160
174 billable_acc += sum(self.entry_durations(start_date, end_date=end_date, rounding=rounding, client_ids=None), start=timedelta(milliseconds=0)) - sum(self.entry_durations(start_date, end_date=end_date, rounding=rounding, client_ids=frozenset(map(lambda c: c['id'], r.json()))), start=timedelta(milliseconds=0)) 161 def get_running_entry(self) -> Any | None:
175 162 kimai_entries = self._session.get('/api/timesheets/active').json()
176 billable_acc += sum(self.entry_durations(start_date, end_date=end_date, rounding=rounding, client_ids=frozenset(*(self._client_ids - {0}))), start=timedelta(milliseconds=0)) 163 if not kimai_entries:
177 164 return None
178 return billable_acc 165 entry = kimai_entries[0]
179 166
180 def get_running_clock(self, now=datetime.now(timezone.utc)): 167 if entry['project']['customer']['id'] not in self._client_ids:
181 url = self._make_url(api = TogglAPISection.TOGGL, section = ['me', 'time_entries', 'current']) 168 return None
182 r = self._query(url = url, method='GET')
183 169
184 if not r or (not r.json() and r.json() is not None): 170 return entry
185 raise TogglAPIError(r)
186 171
187 if not r.json() or not r.json()['billable']: 172 def get_running_clock(self, now: datetime = datetime.now(timezone.utc)) -> timedelta | None:
173 entry = self.get_running_entry()
174 if not entry:
188 return None 175 return None
176 start = isoparse(entry['begin'])
177 return now - start if start <= now else None
189 178
190 if self._client_ids is not None: 179 def get_recent_entries(self) -> Generator[Any]:
191 if 'pid' in r.json() and r.json()['pid']: 180 step = timedelta(days = 7)
192 url = self._make_url(api = TogglAPISection.TOGGL, section = ['projects', str(r.json()['pid'])]) 181 now = datetime.now().astimezone(timezone.utc)
193 pr = self._query(url = url, method = 'GET') 182 ids = set()
194 if not pr or not pr.json(): 183 for req_end in (now - step * i for i in count()):
195 raise TogglAPIError(pr) 184 params = {
196 185 'begin': self.render_datetime(req_end - step),
197 if not pr.json(): 186 'end': self.render_datetime(req_end),
198 return None 187 'full': 'true',
188 }
189 for entry in self.get_timesheets(params):
190 if entry['id'] in ids:
191 continue
192 ids.add(entry['id'])
193 yield entry
199 194
200 if 'cid' in pr.json() and pr.json()['cid']: 195 def start_clock(self, project_id: int, activity_id: int, description: str | None = None, tags: Iterable[str] | None = None, billable: bool = True):
201 if pr.json()['cid'] not in self._client_ids: 196 self._session.post('/api/timesheets', json={
202 return None 197 'begin': self.render_datetime(datetime.now()),
203 elif 0 not in self._client_ids: 198 'project': project_id,
204 return None 199 'activity': activity_id,
205 elif 0 not in self._client_ids: 200 'description': description if description else '',
206 return None 201 'tags': (','.join(tags)) if tags else '',
202 'billable': billable,
203 }).raise_for_status()
207 204
208 start = isoparse(r.json()['start']) 205 def stop_clock(self, running_id: int):
206 self._session.patch(f'/api/timesheets/{running_id}/stop').raise_for_status()
209 207
210 return now - start if start <= now else None
211 208
212class Worktime(object): 209class Worktime(object):
213 time_worked = timedelta() 210 time_worked = timedelta()
@@ -223,6 +220,7 @@ class Worktime(object):
223 leave_budget = dict() 220 leave_budget = dict()
224 time_per_day = None 221 time_per_day = None
225 workdays = None 222 workdays = None
223 pull_forward = dict()
226 224
227 @staticmethod 225 @staticmethod
228 @cache 226 @cache
@@ -279,10 +277,10 @@ class Worktime(object):
279 277
280 config = Worktime.config() 278 config = Worktime.config()
281 config_dir = BaseDirectory.load_first_config('worktime') 279 config_dir = BaseDirectory.load_first_config('worktime')
282 api = TogglAPI( 280 api = KimaiAPI(
283 api_token=config.get("TOGGL", {}).get("ApiToken", None), 281 base_url=config.get("KIMAI", {}).get("BaseUrl", None),
284 workspace_id=config.get("TOGGL", {}).get("Workspace", None), 282 api_token=config.get("KIMAI", {}).get("ApiToken", None),
285 client_ids=config.get("TOGGL", {}).get("ClientIds", None) 283 clients=config.get("KIMAI", {}).get("Clients", None)
286 ) 284 )
287 date_format = config.get("WORKTIME", {}).get("DateFormat", '%Y-%m-%d') 285 date_format = config.get("WORKTIME", {}).get("DateFormat", '%Y-%m-%d')
288 286
@@ -390,7 +388,7 @@ class Worktime(object):
390 if e.errno != 2: 388 if e.errno != 2:
391 raise e 389 raise e
392 390
393 pull_forward = dict() 391 self.time_per_day = lambda day: timedelta(hours = hours_per_week(day)) / len(self.workdays) - (holidays[day] if day in holidays else timedelta())
394 392
395 start_day = self.start_date.date() 393 start_day = self.start_date.date()
396 end_day = self.end_date.date() 394 end_day = self.end_date.date()
@@ -418,21 +416,19 @@ class Worktime(object):
418 if not d == datetime.strptime(c, date_format).replace(tzinfo=tzlocal()).date(): break 416 if not d == datetime.strptime(c, date_format).replace(tzinfo=tzlocal()).date(): break
419 else: 417 else:
420 if d >= self.end_date.date(): 418 if d >= self.end_date.date():
421 pull_forward[d] = min(timedelta(hours = float(hours)), self.time_per_day(d) - (holidays[d] if d in holidays else timedelta())) 419 self.pull_forward[d] = min(timedelta(hours = float(hours)), self.time_per_day(d) - (holidays[d] if d in holidays else timedelta()))
422 except IOError as e: 420 except IOError as e:
423 if e.errno != 2: 421 if e.errno != 2:
424 raise e 422 raise e
425 423
426 self.days_to_work = dict() 424 self.days_to_work = dict()
427 425
428 if pull_forward: 426 if self.pull_forward:
429 end_day = max(end_day, max(list(pull_forward))) 427 end_day = max(end_day, max(list(self.pull_forward)))
430 428
431 for day in [start_day + timedelta(days = x) for x in range(0, (end_day - start_day).days + 1)]: 429 for day in [start_day + timedelta(days = x) for x in range(0, (end_day - start_day).days + 1)]:
432 if day.isoweekday() in self.workdays: 430 if day.isoweekday() in self.workdays:
433 time_to_work = self.time_per_day(day) 431 time_to_work = self.time_per_day(day)
434 if day in holidays.keys():
435 time_to_work -= holidays[day]
436 if time_to_work > timedelta(): 432 if time_to_work > timedelta():
437 self.days_to_work[day] = time_to_work 433 self.days_to_work[day] = time_to_work
438 434
@@ -470,17 +466,17 @@ class Worktime(object):
470 self.extra_days_to_work[self.now.date()] = timedelta() 466 self.extra_days_to_work[self.now.date()] = timedelta()
471 467
472 self.time_to_work = sum([self.days_to_work[day] for day in self.days_to_work.keys() if day <= self.end_date.date()], timedelta()) 468 self.time_to_work = sum([self.days_to_work[day] for day in self.days_to_work.keys() if day <= self.end_date.date()], timedelta())
473 for day in [d for d in list(pull_forward) if d > self.end_date.date()]: 469 for day in [d for d in list(self.pull_forward) if d > self.end_date.date()]:
474 days_forward = set([d for d in self.days_to_work.keys() if d >= self.end_date.date() and d < day and (not d in pull_forward or d == self.end_date.date())]) 470 days_forward = set([d for d in self.days_to_work.keys() if d >= self.end_date.date() and d < day and (not d in self.pull_forward or d == self.end_date.date())])
475 extra_days_forward = set([d for d in self.extra_days_to_work.keys() if d >= self.end_date.date() and d < day and (not d in pull_forward or d == self.end_date.date())]) 471 extra_days_forward = set([d for d in self.extra_days_to_work.keys() if d >= self.end_date.date() and d < day and (not d in self.pull_forward or d == self.end_date.date())])
476 days_forward = days_forward.union(extra_days_forward) 472 days_forward = days_forward.union(extra_days_forward)
477 473
478 extra_day_time_left = timedelta() 474 extra_day_time_left = timedelta()
479 for extra_day in extra_days_forward: 475 for extra_day in extra_days_forward:
480 day_time = max(timedelta(), self.time_per_day(extra_day) - self.extra_days_to_work[extra_day]) 476 day_time = max(timedelta(), self.time_per_day(extra_day) - self.extra_days_to_work[extra_day])
481 extra_day_time_left += day_time 477 extra_day_time_left += day_time
482 extra_day_time = min(extra_day_time_left, pull_forward[day]) 478 extra_day_time = min(extra_day_time_left, self.pull_forward[day])
483 time_forward = pull_forward[day] - extra_day_time 479 time_forward = self.pull_forward[day] - extra_day_time
484 if extra_day_time_left > timedelta(): 480 if extra_day_time_left > timedelta():
485 for extra_day in extra_days_forward: 481 for extra_day in extra_days_forward:
486 day_time = max(timedelta(), self.time_per_day(extra_day) - self.extra_days_to_work[extra_day]) 482 day_time = max(timedelta(), self.time_per_day(extra_day) - self.extra_days_to_work[extra_day])
@@ -496,7 +492,7 @@ class Worktime(object):
496 492
497 self.time_to_work += self.time_pulled_forward 493 self.time_to_work += self.time_pulled_forward
498 494
499 self.time_worked += api.get_billable_hours(self.start_date, self.now, rounding = config.get("WORKTIME", {}).get("rounding", True)) 495 self.time_worked += api.get_billable_hours(self.start_date, self.now)
500 496
501def format_days(worktime, days, date_format=None): 497def format_days(worktime, days, date_format=None):
502 if not date_format: 498 if not date_format:
@@ -518,7 +514,14 @@ def format_days(worktime, days, date_format=None):
518 return ', '.join(map(lambda group: ','.join(map(format_group, group)), groups)) 514 return ', '.join(map(lambda group: ','.join(map(format_group, group)), groups))
519 515
520 516
521def worktime(**args): 517def tooltip_timedelta(td):
518 if td < timedelta(seconds = 0):
519 return "-" + tooltip_timedelta(-td)
520 mm, ss = divmod(td.total_seconds(), 60)
521 hh, mm = divmod(mm, 60)
522 return "%d:%02d:%02d" % (hh, mm, ss)
523
524def worktime(pull_forward_cutoff, waybar, **args):
522 worktime = Worktime(**args) 525 worktime = Worktime(**args)
523 526
524 def format_worktime(worktime): 527 def format_worktime(worktime):
@@ -557,24 +560,41 @@ def worktime(**args):
557 return f"{indicator}{difference_string}" 560 return f"{indicator}{difference_string}"
558 else: 561 else:
559 difference_string = difference_string(total_minutes_difference * timedelta(minutes = 1)) 562 difference_string = difference_string(total_minutes_difference * timedelta(minutes = 1))
560 if worktime.now_is_workday: 563 return difference_string
561 return difference_string 564
562 else: 565 out_class = "running" if worktime.running_entry else "stopped"
563 return f"({difference_string})" 566 difference = worktime.time_to_work - worktime.time_worked
564 567 if worktime.running_entry and -min(timedelta(milliseconds=0), difference) > sum(worktime.pull_forward.values(), start=timedelta(milliseconds=0)) or not worktime.running_entry and max(timedelta(milliseconds=0), difference) > worktime.time_per_day(worktime.now.date()) and worktime.now_is_workday:
565 if worktime.time_pulled_forward >= timedelta(minutes = 15): 568 out_class = "over"
569 pull_forward_sum = sum(worktime.pull_forward.values(), start=timedelta(milliseconds=0))
570 if pull_forward_sum >= min(pull_forward_cutoff, timedelta(seconds = 1)):
566 worktime_no_pulled_forward = deepcopy(worktime) 571 worktime_no_pulled_forward = deepcopy(worktime)
567 worktime_no_pulled_forward.time_to_work -= worktime_no_pulled_forward.time_pulled_forward 572 worktime_no_pulled_forward.time_to_work -= worktime_no_pulled_forward.time_pulled_forward
568 worktime_no_pulled_forward.time_pulled_forward = timedelta() 573 worktime_no_pulled_forward.time_pulled_forward = timedelta()
574 worktime_no_pulled_forward.pull_forward = dict()
575 worktime.time_to_work += pull_forward_sum
569 576
570 difference_string = format_worktime(worktime)
571 difference_string_no_pulled_forward = format_worktime(worktime_no_pulled_forward) 577 difference_string_no_pulled_forward = format_worktime(worktime_no_pulled_forward)
572 578
573 print(f"{difference_string_no_pulled_forward}…{difference_string}") 579 tooltip = tooltip_timedelta(worktime_no_pulled_forward.time_to_work - worktime_no_pulled_forward.time_worked) + "…" + tooltip_timedelta(difference + pull_forward_sum)
580 if pull_forward_sum >= pull_forward_cutoff:
581 out_text = f"{difference_string_no_pulled_forward}…{format_worktime(worktime)}"
582 else:
583 out_text = format_worktime(worktime)
574 else: 584 else:
575 print(format_worktime(worktime)) 585 tooltip = tooltip_timedelta(difference)
586 out_text = format_worktime(worktime)
587
588 if waybar:
589 json.dump({"text": out_text, "class": out_class, "tooltip": tooltip}, stdout)
590 else:
591 print(out_text)
592
593def pull_forward(**args):
594 worktime = Worktime(**args)
595 print(tooltip_timedelta(sum(worktime.pull_forward.values(), start=timedelta(milliseconds=0))))
576 596
577def time_worked(now, **args): 597def time_worked(now, waybar, **args):
578 then = now.replace(hour = 0, minute = 0, second = 0, microsecond = 0) 598 then = now.replace(hour = 0, minute = 0, second = 0, microsecond = 0)
579 if now.time() == time(): 599 if now.time() == time():
580 now = now + timedelta(days = 1) 600 now = now + timedelta(days = 1)
@@ -584,33 +604,62 @@ def time_worked(now, **args):
584 604
585 worked = now.time_worked - then.time_worked 605 worked = now.time_worked - then.time_worked
586 606
607 out_text = None
608 out_class = "running" if now.running_entry else "stopped"
609 tooltip = tooltip_timedelta(worked)
610 target_time = max(then.time_per_day(then.now.date()), now.time_per_day(now.now.date())) if then.time_per_day(then.now.date()) and now.time_per_day(now.now.date()) else (then.time_per_day(then.now.date()) if then.time_per_day(then.now.date()) else now.time_per_day(now.now.date()));
611 difference = target_time - worked
612 difference_pull_forward = difference + now.time_pulled_forward
613 if now.running_entry and difference_pull_forward < timedelta(seconds=0):
614 out_class = "over"
587 if args['do_round']: 615 if args['do_round']:
588 total_minutes_difference = 5 * ceil(worked / timedelta(minutes = 5)) 616 total_minutes_difference = 5 * ceil(worked / timedelta(minutes = 5))
589 (hours_difference, minutes_difference) = divmod(abs(total_minutes_difference), 60) 617 (hours_difference, minutes_difference) = divmod(abs(total_minutes_difference), 60)
590 sign = '' if total_minutes_difference >= 0 else '-' 618 sign = '' if total_minutes_difference >= 0 else '-'
591
592 difference_string = f"{sign}"
593 if hours_difference != 0:
594 difference_string += f"{hours_difference}h"
595 if hours_difference == 0 or minutes_difference != 0:
596 difference_string += f"{minutes_difference}m"
597
598 clockout_time = None
599 clockout_difference = None
600 if then.now_is_workday or now.now_is_workday:
601 target_time = max(then.time_per_day(then.now.date()), now.time_per_day(now.now.date())) if then.time_per_day(then.now.date()) and now.time_per_day(now.now.date()) else (then.time_per_day(then.now.date()) if then.time_per_day(then.now.date()) else now.time_per_day(now.now.date()));
602 difference = target_time - worked
603 clockout_difference = 5 * ceil(difference / timedelta(minutes = 5))
604 clockout_time = now.now + difference
605 clockout_time += (5 - clockout_time.minute % 5) * timedelta(minutes = 1)
606 clockout_time = clockout_time.replace(second = 0, microsecond = 0)
607 619
608 if now.running_entry and clockout_time and clockout_difference >= 0: 620 difference_string = f"{sign}"
609 print(f"{difference_string}/{clockout_time:%H:%M}") 621 if hours_difference != 0:
610 else: 622 difference_string += f"{hours_difference}h"
611 print(difference_string) 623 if hours_difference == 0 or minutes_difference != 0:
624 difference_string += f"{minutes_difference}m"
625
626 def round_clockout_time(difference):
627 clockout_time = None
628 clockout_difference = None
629 exact_clockout_time = None
630 if then.now_is_workday or now.now_is_workday:
631 clockout_difference = 5 * ceil(difference / timedelta(minutes = 5))
632 clockout_time = now.now + difference
633 exact_clockout_time = clockout_time
634 clockout_time += (5 - clockout_time.minute % 5) * timedelta(minutes = 1)
635 clockout_time = clockout_time.replace(second = 0, microsecond = 0)
636
637 return clockout_time, exact_clockout_time, clockout_difference
638
639 clockout_time, exact_clockout_time, clockout_difference = round_clockout_time(difference)
640 clockout_time_pull_forward, exact_clockout_time_pull_forward, clockout_difference_pull_forward = round_clockout_time(difference_pull_forward)
641 clockout_pull_forward_sum, exact_clockout_pull_forward_sum, _ = round_clockout_time(now.time_to_work - now.time_worked + sum(now.pull_forward.values(), start=timedelta(milliseconds=0)))
642
643 if now.running_entry and clockout_time and (clockout_difference >= 0 or clockout_difference_pull_forward >= 0):
644 out_text = f"{difference_string}/{clockout_time:%H:%M}"
645 tooltip = f"{tooltip_timedelta(worked)}/{exact_clockout_time:%H:%M:%S}"
646
647 if clockout_pull_forward_sum >= clockout_time_pull_forward and clockout_time_pull_forward != clockout_time:
648 out_text += f"…{clockout_time_pull_forward:%H:%M}"
649 if exact_clockout_pull_forward_sum >= exact_clockout_time_pull_forward and exact_clockout_time_pull_forward != exact_clockout_time:
650 tooltip += f"…{exact_clockout_time_pull_forward:%H:%M:%S}"
651 else:
652 out_text = difference_string
653 else:
654 out_text = str(worked)
655
656 if not now.now_is_workday:
657 out_text = f'({out_text})'
658
659 if waybar:
660 json.dump({"text": out_text, "class": out_class, "tooltip": tooltip}, stdout)
612 else: 661 else:
613 print(worked) 662 print(out_text)
614 663
615def diff(now, **args): 664def diff(now, **args):
616 now = now.replace(hour = 0, minute = 0, second = 0, microsecond = 0) 665 now = now.replace(hour = 0, minute = 0, second = 0, microsecond = 0)
@@ -798,18 +847,54 @@ def classification(classification_name, table, table_format, **args):
798def main(): 847def main():
799 def isotime(s): 848 def isotime(s):
800 return datetime.fromisoformat(s).replace(tzinfo=tzlocal()) 849 return datetime.fromisoformat(s).replace(tzinfo=tzlocal())
850 def duration_minutes(s):
851 return timedelta(minutes = float(s))
852
853 def set_default_subparser(self, name, args=None, positional_args=0):
854 """default subparser selection. Call after setup, just before parse_args()
855 name: is the name of the subparser to call by default
856 args: if set is the argument list handed to parse_args()
857
858 , tested with 2.7, 3.2, 3.3, 3.4
859 it works with 2.6 assuming argparse is installed
860 """
861 subparser_found = False
862 for arg in sys.argv[1:]:
863 if arg in ['-h', '--help']: # global help if no subparser
864 break
865 else:
866 for x in self._subparsers._actions:
867 if not isinstance(x, argparse._SubParsersAction):
868 continue
869 for sp_name in x._name_parser_map.keys():
870 if sp_name in sys.argv[1:]:
871 subparser_found = True
872 if not subparser_found:
873 # insert default in last position before global positional
874 # arguments, this implies no global options are specified after
875 # first positional argument
876 if args is None:
877 sys.argv.insert(len(sys.argv) - positional_args, name)
878 else:
879 args.insert(len(args) - positional_args, name)
880
881 argparse.ArgumentParser.set_default_subparser = set_default_subparser
801 882
802 config = Worktime.config() 883 config = Worktime.config()
803 884
804 parser = argparse.ArgumentParser(prog = "worktime", description = 'Track worktime using toggl API') 885 parser = argparse.ArgumentParser(prog = "worktime", description = 'Track worktime using Kimai API')
805 parser.add_argument('--time', dest = 'now', metavar = 'TIME', type = isotime, help = 'Time to calculate status for (default: current time)', default = datetime.now(tzlocal())) 886 parser.add_argument('--time', dest = 'now', metavar = 'TIME', type = isotime, help = 'Time to calculate status for (default: current time)', default = datetime.now(tzlocal()))
806 parser.add_argument('--start', dest = 'start_datetime', metavar = 'TIME', type = isotime, help = 'Time to calculate status from (default: None)', default = None) 887 parser.add_argument('--start', dest = 'start_datetime', metavar = 'TIME', type = isotime, help = 'Time to calculate status from (default: None)', default = None)
807 parser.add_argument('--no-running', dest = 'include_running', action = 'store_false') 888 parser.add_argument('--no-running', dest = 'include_running', action = 'store_false')
808 parser.add_argument('--no-force-day-to-work', dest = 'force_day_to_work', action = 'store_false') 889 parser.add_argument('--no-force-day-to-work', dest = 'force_day_to_work', action = 'store_false')
809 subparsers = parser.add_subparsers(help = 'Subcommands') 890 subparsers = parser.add_subparsers(help = 'Subcommands')
810 parser.set_defaults(cmd = worktime) 891 worktime_parser = subparsers.add_parser('time_worked', aliases = ['time', 'worked'])
811 time_worked_parser = subparsers.add_parser('time_worked', aliases = ['time', 'worked', 'today']) 892 worktime_parser.add_argument('--pull-forward-cutoff', dest = 'pull_forward_cutoff', metavar = 'MINUTES', type = duration_minutes, default = timedelta(minutes = 15))
893 worktime_parser.add_argument('--waybar', action='store_true')
894 worktime_parser.set_defaults(cmd = worktime)
895 time_worked_parser = subparsers.add_parser('today')
812 time_worked_parser.add_argument('--no-round', dest = 'do_round', action = 'store_false') 896 time_worked_parser.add_argument('--no-round', dest = 'do_round', action = 'store_false')
897 time_worked_parser.add_argument('--waybar', action='store_true')
813 time_worked_parser.set_defaults(cmd = time_worked) 898 time_worked_parser.set_defaults(cmd = time_worked)
814 diff_parser = subparsers.add_parser('diff') 899 diff_parser = subparsers.add_parser('diff')
815 diff_parser.set_defaults(cmd = diff) 900 diff_parser.set_defaults(cmd = diff)
@@ -827,9 +912,146 @@ def main():
827 classification_parser.add_argument('--table', action = 'store_true') 912 classification_parser.add_argument('--table', action = 'store_true')
828 classification_parser.add_argument('--table-format', dest='table_format', type=str, default='fancy_grid') 913 classification_parser.add_argument('--table-format', dest='table_format', type=str, default='fancy_grid')
829 classification_parser.set_defaults(cmd = partial(classification, classification_name=classification_name)) 914 classification_parser.set_defaults(cmd = partial(classification, classification_name=classification_name))
915 pull_forward_parser = subparsers.add_parser('pull-forward')
916 pull_forward_parser.set_defaults(cmd = pull_forward)
917 parser.set_default_subparser('time_worked')
830 args = parser.parse_args() 918 args = parser.parse_args()
831 919
832 args.cmd(**vars(args)) 920 args.cmd(**vars(args))
833 921
922async def ui_update_options(api, cache_path):
923 options = set()
924 sort_order = dict()
925 entry_iter = enumerate(api.get_recent_entries())
926 loop = asyncio.get_event_loop()
927 start = clock_gettime_ns(CLOCK_MONOTONIC)
928 while item := await loop.run_in_executor(None, next, entry_iter):
929 ix, entry = item
930 if len(options) >= 20 or ix >= 1000:
931 break
932 elif len(options) >= 3:
933 now = clock_gettime_ns(CLOCK_MONOTONIC)
934 if now - start >= 4000000000:
935 break
936
937 option = frozendict({
938 'tags': frozenset(entry['tags']),
939 'activity': frozendict({'id': entry['activity']['id'], 'name': entry['activity']['name']}),
940 'project': frozendict({'id': entry['project']['id'], 'customer': entry['project']['customer']['name'], 'name': entry['project']['name']}),
941 'description': entry['description'] if entry['description'] else None,
942 'billable': entry['billable'],
943 })
944 sort_value = isoparse(entry['begin'])
945 if option in sort_order:
946 sort_value = max(sort_value, sort_order[option])
947 sort_order[option] = sort_value
948 options.add(option)
949
950 options = list(sorted(options, key = lambda o: sort_order[o], reverse = True))
951
952 with AtomicWriter(cache_path, overwrite=True) as ch:
953 ch.write_text(jsonpickle.encode(options))
954
955 return options
956
957def ui_render_option(option):
958 res = ''
959 if option['description']:
960 res += '„{}“, '.format(option['description'])
961 res += option['activity']['name'] + ', '
962 res += option['project']['name']
963 if option['project']['customer'] not in option['project']['name']:
964 res += ' ({})'.format(option['project']['customer'])
965 if option['tags']:
966 res += ', {}'.format(' '.join(map(lambda t: '#{}'.format(t), option['tags'])))
967 if not option['billable']:
968 res += ', not billable'
969 return res
970
971async def ui_main():
972 cache_path = Path(BaseDirectory.save_cache_path('worktime-ui')) / 'options.json'
973 options = None
974 try:
975 with cache_path.open('r', encoding='utf-8') as ch:
976 options = jsonpickle.decode(ch.read())
977 except FileNotFoundError:
978 pass
979
980 config = Worktime.config()
981 api = KimaiAPI(
982 base_url=config.get("KIMAI", {}).get("BaseUrl", None),
983 api_token=config.get("KIMAI", {}).get("ApiToken", None),
984 clients=config.get("KIMAI", {}).get("Clients", None)
985 )
986 running_entry = api.get_running_entry()
987
988 async with asyncio.TaskGroup() as tg:
989 update_options = tg.create_task(ui_update_options(api, cache_path))
990 if not options:
991 options = await update_options
992
993 read_fd, write_fd = os.pipe()
994 w_pipe = open(write_fd, 'wb', 0)
995 loop = asyncio.get_event_loop()
996 w_transport, _ = await loop.connect_write_pipe(
997 asyncio.Protocol,
998 w_pipe,
999 )
1000 r_pipe = open(read_fd, 'rb', 0)
1001
1002 proc = await asyncio.create_subprocess_exec(
1003 "fuzzel", "--dmenu", "--index", "--width=60",
1004 stdout = asyncio.subprocess.PIPE,
1005 stdin = r_pipe,
1006 )
1007
1008 with closing(w_transport) as t:
1009 if running_entry:
1010 t.write(b'Stop running timesheet\n')
1011 for option in options:
1012 t.write(ui_render_option(option).encode('utf-8') + b'\n')
1013
1014 stdout, _ = await proc.communicate()
1015 if proc.returncode != 0:
1016 return
1017 fuzzel_out = int(stdout.decode('utf-8'))
1018 if fuzzel_out < 0 or fuzzel_out >= len(options):
1019 return
1020 elif running_entry and fuzzel_out == 0:
1021 api.stop_clock(running_entry['id'])
1022 await notify.Server('worktime').Notify("Stopped running timesheet").set_timeout(65000).show()
1023 else:
1024 if running_entry:
1025 fuzzel_out -= 1
1026 option = options[fuzzel_out]
1027 api.start_clock(
1028 project_id = option['project']['id'],
1029 activity_id = option['activity']['id'],
1030 description = option['description'],
1031 tags = option['tags'],
1032 billable = option['billable'],
1033 )
1034 await notify.Server('worktime').Notify("Timesheet started…").set_timeout(65000).show()
1035
1036
1037def ui():
1038 asyncio.run(ui_main())
1039
1040async def stop_main():
1041 config = Worktime.config()
1042 api = KimaiAPI(
1043 base_url=config.get("KIMAI", {}).get("BaseUrl", None),
1044 api_token=config.get("KIMAI", {}).get("ApiToken", None),
1045 clients=config.get("KIMAI", {}).get("Clients", None)
1046 )
1047 if running_entry := api.get_running_entry():
1048 api.stop_clock(running_entry['id'])
1049 await notify.Server('worktime').Notify("Stopped running timesheet").set_timeout(65000).show()
1050 else:
1051 await notify.Server('worktime').Notify("No timesheet currently running").set_timeout(65000).show()
1052
1053def stop():
1054 asyncio.run(stop_main())
1055
834if __name__ == "__main__": 1056if __name__ == "__main__":
835 sys.exit(main()) 1057 sys.exit(main())
diff --git a/overlays/zte-prometheus-exporter/default.nix b/overlays/zte-prometheus-exporter/default.nix
index 2188e7b3..cd4207cd 100644
--- a/overlays/zte-prometheus-exporter/default.nix
+++ b/overlays/zte-prometheus-exporter/default.nix
@@ -2,17 +2,16 @@
2let 2let
3 packageOverrides = final.callPackage ./python-packages.nix {}; 3 packageOverrides = final.callPackage ./python-packages.nix {};
4 inpPython = final.python310.override { inherit packageOverrides; }; 4 inpPython = final.python310.override { inherit packageOverrides; };
5 python = inpPython.withPackages (ps: with ps; [pytimeparse requests]);
5in { 6in {
6 zte-prometheus-exporter = prev.stdenv.mkDerivation rec { 7 zte-prometheus-exporter = prev.stdenv.mkDerivation rec {
7 name = "zte-prometheus-exporter"; 8 name = "zte-prometheus-exporter";
8 src = ./zte-prometheus-exporter.py; 9 src = prev.replaceVars ./zte-prometheus-exporter.py { inherit python; };
9 10
10 phases = [ "buildPhase" "checkPhase" "installPhase" ]; 11 phases = [ "unpackPhase" "checkPhase" "installPhase" ];
11 12
12 python = inpPython.withPackages (ps: with ps; [pytimeparse requests]); 13 unpackPhase = ''
13 14 cp $src zte-prometheus-exporter
14 buildPhase = ''
15 substituteAll $src zte-prometheus-exporter
16 ''; 15 '';
17 16
18 doCheck = true; 17 doCheck = true;
diff --git a/shell.nix b/shell.nix
index 5bcb8180..1b334187 100644
--- a/shell.nix
+++ b/shell.nix
@@ -1,19 +1,29 @@
1inputs@{ system, self, deploy-rs, nvfetcher, nixpkgs, ca-util, ... }: 1inputs@{ system, self, nvfetcher, nixpkgs, ca-util, ... }:
2let 2let
3 pkgs = self.legacyPackages.${system}; 3 pkgs = self.legacyPackages.${system};
4 utils = import ./utils { inherit (nixpkgs) lib; }; 4 utils = import ./utils { inherit (nixpkgs) lib; };
5 inherit (utils) nixImport; 5 inherit (utils) nixImport;
6 uv-links = pkgs.symlinkJoin {
7 name = "uv-links";
8 paths = [
9 pkgs.python312.pkgs.pygobject3
10 ];
11 };
6in pkgs.mkShell { 12in pkgs.mkShell {
7 nativeBuildInputs = builtins.attrValues self.packages.${system} ++ (with pkgs; [ 13 nativeBuildInputs = builtins.attrValues self.packages.${system} ++ (with pkgs; [
8 sops 14 sops
9 wireguard-tools 15 wireguard-tools
10 gup 16 gup
11 nftables 17 nftables
12 deploy-rs.packages.${system}.deploy-rs 18 deploy-rs.deploy-rs
13 knot-dns 19 knot-dns
14 yq 20 yq
15 nvfetcher.packages.${system}.default 21 nvfetcher.packages.${system}.default
16 ca-util.packages.${system}.ca 22 ca-util.packages.${system}.ca
17 poetry 23 poetry uv
24 ninja pkg-config cairo.dev systemd.dev
18 ]); 25 ]);
26 shellHook = ''
27 export UV_FIND_LINKS=${uv-links}/lib/python3.12/site-packages
28 '';
19} 29}
diff --git a/system-profiles/core/default.nix b/system-profiles/core/default.nix
index b85aea4e..e5f9dc16 100644
--- a/system-profiles/core/default.nix
+++ b/system-profiles/core/default.nix
@@ -127,36 +127,16 @@ in {
127 127
128 flake-registry = "${flakeInputs.flake-registry}/flake-registry.json"; 128 flake-registry = "${flakeInputs.flake-registry}/flake-registry.json";
129 }; 129 };
130 nixPath = [ 130 nixPath = map (flake: "${flake}=flake:${flake}") (attrNames config.nix.registry);
131 "nixpkgs=${pkgs.runCommand "nixpkgs" {} ''
132 mkdir $out
133 ln -s ${./nixpkgs.nix} $out/default.nix
134 ln -s /run/nixpkgs/lib $out/lib
135 ''}"
136 ];
137 registry = 131 registry =
138 let override = { self = "nixos"; }; 132 let override = { self = "nixos"; };
139 in mapAttrs' (inpName: inpFlake: nameValuePair 133 in mapAttrs' (inpName: inpFlake: nameValuePair
140 (override.${inpName} or inpName) 134 (override.${inpName} or inpName)
141 { flake = inpFlake; } ) flakeInputs; 135 { to = { type = "path"; path = inpFlake; }; } ) flakeInputs;
142 }; 136 };
143 137
144 systemd.tmpfiles.rules = [ 138 systemd.tmpfiles.rules = [
145 "L+ /run/nixpkgs - - - - ${flakeInputs.${config.nixpkgs.flakeInput}.outPath}" 139 "L+ /run/nixpkgs - - - - ${flakeInputs.${config.nixpkgs.flakeInput}.outPath}"
146 "L+ /run/nixpkgs-overlays.nix - - - - ${pkgs.writeText "overlays.nix" ''
147 with builtins;
148
149 attrValues (import
150 (
151 let lock = fromJSON (readFile ${flake + "/flake.lock"}); in
152 fetchTarball {
153 url = "https://github.com/edolstra/flake-compat/archive/''${lock.nodes.flake-compat.locked.rev}.tar.gz";
154 sha256 = lock.nodes.flake-compat.locked.narHash;
155 }
156 )
157 { src = ${flake}; }
158 ).defaultNix.overlays
159 ''}"
160 "L+ /etc/nixos - - - - ${flake}" 140 "L+ /etc/nixos - - - - ${flake}"
161 ] ++ map (input: "L+ /run/flake-inputs/${input} - - - - ${flakeInputs.${input}.outPath}") (attrNames flakeInputs); 141 ] ++ map (input: "L+ /run/flake-inputs/${input} - - - - ${flakeInputs.${input}.outPath}") (attrNames flakeInputs);
162 142
@@ -177,8 +157,6 @@ in {
177 { 157 {
178 manual.manpages.enable = true; 158 manual.manpages.enable = true;
179 systemd.user.startServices = "sd-switch"; 159 systemd.user.startServices = "sd-switch";
180
181 programs.ssh.internallyManaged = mkForce true;
182 } 160 }
183 ]; 161 ];
184 extraSpecialArgs = { inherit flake flakeInputs path; hostConfig = config; }; 162 extraSpecialArgs = { inherit flake flakeInputs path; hostConfig = config; };
@@ -202,13 +180,7 @@ in {
202 }; 180 };
203 environment.systemPackages = with pkgs; [ git-annex scutiger ]; 181 environment.systemPackages = with pkgs; [ git-annex scutiger ];
204 } 182 }
205 ] ++ (optional (options ? system.switch.enableNg) { 183 ] ++ (optional (options ? system.rebuild.enableNg) {
206 system.switch = lib.mkDefault {
207 enable = false;
208 enableNg = true;
209 };
210 })
211 ++ (optional (options ? system.rebuild.enableNg) {
212 system.rebuild.enableNg = lib.mkDefault true; 184 system.rebuild.enableNg = lib.mkDefault true;
213 }) 185 })
214 ++ (optional (options ? services.userborn) { 186 ++ (optional (options ? services.userborn) {
diff --git a/system-profiles/default-locale.nix b/system-profiles/default-locale.nix
index 2d483f04..60d338cb 100644
--- a/system-profiles/default-locale.nix
+++ b/system-profiles/default-locale.nix
@@ -1,16 +1,23 @@
1{ lib, ... }: 1{ lib, options, ... }:
2 2
3with lib; 3with lib;
4 4
5{ 5{
6 i18n = { 6 config = foldr recursiveUpdate {} ([
7 defaultLocale = "en_DK.UTF-8"; 7 {
8 extraLocaleSettings = { 8 i18n = {
9 "TIME_STYLE" = "long-iso"; 9 defaultLocale = "en_DK.UTF-8";
10 }; 10 extraLocaleSettings = {
11 supportedLocales = [ "C.UTF-8/UTF-8" "en_US.UTF-8/UTF-8" "en_DK.UTF-8/UTF-8" ]; 11 "TIME_STYLE" = "long-iso";
12 }; 12 };
13 console.keyMap = mkDefault "dvorak-programmer"; 13 };
14 console.keyMap = mkDefault "dvorak-programmer";
14 15
15 time.timeZone = mkDefault "Europe/Berlin"; 16 time.timeZone = mkDefault "Europe/Berlin";
17 }
18 ] ++ (optional (options ? i18n.extraLocales) {
19 i18n.extraLocales = [ "C.UTF-8" "en_US.UTF-8" "en_DK.UTF-8" ];
20 }) ++ (optional (!(options ? i18n.extraLocales)) {
21 i18n.supportedLocales = [ "C.UTF-8/UTF-8" "en_US.UTF-8/UTF-8" "en_DK.UTF-8/UTF-8" ];
22 }));
16} 23}
diff --git a/system-profiles/nfsroot.nix b/system-profiles/nfsroot.nix
index 1cd930d9..e3dc2d2e 100644
--- a/system-profiles/nfsroot.nix
+++ b/system-profiles/nfsroot.nix
@@ -1,4 +1,4 @@
1{ config, options, pkgs, lib, flake, flakeInputs, ... }: 1{ config, options, pkgs, lib, flake, ... }:
2 2
3with lib; 3with lib;
4 4
@@ -48,7 +48,7 @@ in {
48 fileSystems."/nix/.ro-store" = mkImageMediaOverride 48 fileSystems."/nix/.ro-store" = mkImageMediaOverride
49 { fsType = "nfs4"; 49 { fsType = "nfs4";
50 device = cfg.storeDevice; 50 device = cfg.storeDevice;
51 options = [ "ro" ]; 51 options = [ "ro" "nfsvers=4.2" ];
52 neededForBoot = true; 52 neededForBoot = true;
53 }; 53 };
54 54
@@ -86,7 +86,7 @@ in {
86 mkdir -p /mnt-root/etc/ 86 mkdir -p /mnt-root/etc/
87 cp /etc/resolv.conf /mnt-root/etc/resolv.conf 87 cp /etc/resolv.conf /mnt-root/etc/resolv.conf
88 ''; 88 '';
89 networking.useDHCP = true; 89 networking.useDHCP = mkImageMediaOverride true;
90 networking.resolvconf.enable = false; 90 networking.resolvconf.enable = false;
91 networking.dhcpcd.persistent = true; 91 networking.dhcpcd.persistent = true;
92 92
diff --git a/system-profiles/rebuild-machines/default.nix b/system-profiles/rebuild-machines/default.nix
index 544f47e1..de86cd74 100644
--- a/system-profiles/rebuild-machines/default.nix
+++ b/system-profiles/rebuild-machines/default.nix
@@ -25,16 +25,18 @@ let
25 25
26 phases = [ "buildPhase" "installPhase" ]; 26 phases = [ "buildPhase" "installPhase" ];
27 27
28 inherit (pkgs) zsh coreutils openssh;
29 inherit (cfg) scriptName;
30 inherit (cfg.flake) flakeOutput;
31 flake = cfg.flake.name;
32 nixosRebuild = config.system.build.nixos-rebuild;
33 inherit (config.security) wrapperDir;
34 inherit sshConfig;
35
36 buildPhase = '' 28 buildPhase = ''
37 substituteAll $src rebuild-machine.zsh 29 substitute $src rebuild-machine.zsh \
30 --subst-var-by zsh ${pkgs.zsh} \
31 --subst-var-by coreutils ${pkgs.coreutils} \
32 --subst-var-by openssh ${pkgs.openssh} \
33 --subst-var-by wrapperDir ${config.security.wrapperDir} \
34 --subst-var-by sshConfig ${sshConfig} \
35 --subst-var-by out "$out" \
36 --subst-var-by nixosRebuild ${config.system.build.nixos-rebuild} \
37 --subst-var-by flake ${cfg.flake.name} \
38 --subst-var-by scriptName ${cfg.scriptName} \
39 --subst-var-by flakeOutput ${cfg.flake.flakeOutput}
38 ''; 40 '';
39 41
40 installPhase = '' 42 installPhase = ''
diff --git a/system-profiles/zfs.nix b/system-profiles/zfs.nix
index 149decee..af9f1c17 100644
--- a/system-profiles/zfs.nix
+++ b/system-profiles/zfs.nix
@@ -1,8 +1,8 @@
1{ pkgs, lib, ... } : { 1{ config, pkgs, lib, ... } : {
2 config = { 2 config = {
3 boot = { 3 boot = {
4 kernelPackages = pkgs.linuxPackages_6_11; 4 kernelPackages = pkgs.linuxPackages_6_12;
5 zfs.package = pkgs.zfs_unstable; 5 zfs.package = pkgs.zfs_2_3;
6 6
7 supportedFilesystems.zfs = true; 7 supportedFilesystems.zfs = true;
8 }; 8 };
diff --git a/user-profiles/core.nix b/user-profiles/core.nix
index a8af48b3..57fb7628 100644
--- a/user-profiles/core.nix
+++ b/user-profiles/core.nix
@@ -5,7 +5,11 @@ with lib;
5{ 5{
6 config = { 6 config = {
7 users.users.${userName} = {}; # Just make sure the user is created 7 users.users.${userName} = {}; # Just make sure the user is created
8 home-manager.users.${userName} = {}; 8 home-manager.users.${userName} = let sysConfig = config; in { config, ... }: {
9 config.nix.settings = {
10 inherit (sysConfig.nix.settings) use-xdg-base-directories;
11 };
12 };
9 13
10 systemd.services."home-manager-${utils.escapeSystemdPath userName}" = lib.mkIf (!config.home-manager.enableSystemd) { 14 systemd.services."home-manager-${utils.escapeSystemdPath userName}" = lib.mkIf (!config.home-manager.enableSystemd) {
11 restartIfChanged = false; # only run once on startup, deploy to running system with deploy-rs 15 restartIfChanged = false; # only run once on startup, deploy to running system with deploy-rs
diff --git a/user-profiles/feeds/alot.config b/user-profiles/feeds/alot.config
deleted file mode 100644
index a14d4539..00000000
--- a/user-profiles/feeds/alot.config
+++ /dev/null
@@ -1,50 +0,0 @@
1attachment_prefix="~/Downloads"
2bug_on_exit=true
3editor_cmd="false"
4tabwidth=2
5timestamp_format="%a %d %b %H:%M:%S %Y UTC%z"
6auto_remove_unread=True
7#initial_command="search ( tag:inbox ) AND NOT ( tag:killed )"
8initial_command="search ( tag:inbox ) AND NOT ( is:link OR is:media OR is:killed )"
9
10[accounts]
11 [[private]]
12 realname = @realname@
13 address = @address@
14
15[bindings]
16j =
17k =
18'g g' =
19G =
20I = search ( tag:inbox ) AND NOT ( is:killed )
21U = search ( tag:inbox ) AND NOT ( is:link OR is:media OR is:killed )
22V = search ( tag:inbox AND is:media OR ( is:live AND date:12h.. AND NOT is:unread ) ) AND NOT ( is:killed )
23W = search ( is:media ) AND NOT ( tag:inbox OR is:killed OR is:highlight )
24L = search ( tag:inbox AND is:link ) AND NOT ( is:killed )
25
26h = move first
27t = move up
28n = move down
29s = move last
30 [[search]]
31 a =
32 s =
33
34 u = toggletags unread
35 i = toggletags inbox
36 j = untag unread,inbox
37 r = toggletags later
38 [[thread]]
39 s =
40 S =
41 n =
42 'g j' =
43 'g k' =
44 'g l' =
45 w = save
46 W = save --all
47 'g h' = move parent
48 'g t' = move next sibling
49 'g n' = move previous sibling
50 'g s' = move first reply \ No newline at end of file
diff --git a/user-profiles/feeds/default.nix b/user-profiles/feeds/default.nix
deleted file mode 100644
index 82be90c7..00000000
--- a/user-profiles/feeds/default.nix
+++ /dev/null
@@ -1,11 +0,0 @@
1{ config, flakeInputs, pkgs, lib, userName, customUtils, ... }:
2{
3 home-manager.users.${userName} = {...}: {
4 imports = [
5 (customUtils.overrideModuleArgs
6 (import ./module.nix)
7 (inputs: inputs // { inherit flakeInputs; inherit (config.nixpkgs) system; })
8 )
9 ];
10 };
11}
diff --git a/user-profiles/feeds/imm-notmuch-insert.py b/user-profiles/feeds/imm-notmuch-insert.py
deleted file mode 100644
index b7eed292..00000000
--- a/user-profiles/feeds/imm-notmuch-insert.py
+++ /dev/null
@@ -1,52 +0,0 @@
1#!@python@/bin/python
2
3import json
4import sys
5import subprocess
6from io import BytesIO
7from email.message import EmailMessage
8import configparser
9from os import environ
10from datetime import *
11from dateutil.tz import *
12from dateutil.parser import isoparse
13from html2text import html2text
14
15def main():
16 notmuchConfig = configparser.ConfigParser()
17 notmuchConfig.read(environ.get('NOTMUCH_CONFIG'))
18
19 callbackMessage = json.load(sys.stdin)
20
21 msg = EmailMessage()
22 authors = ', '.join(map(lambda author: author['name'], callbackMessage['feed_item']['authors']))
23 if authors:
24 msg['From'] = f"{callbackMessage['feed_definition']['title']} ({authors}) <imm@imm.invalid>"
25 else:
26 msg['From'] = f"{callbackMessage['feed_definition']['title']} <imm@imm.invalid>"
27 msg['To'] = f"{notmuchConfig['user']['name']} <{notmuchConfig['user']['primary_email']}>"
28 if 'title' in callbackMessage['feed_item'] and callbackMessage['feed_item']['title']:
29 msg['Subject'] = callbackMessage['feed_item']['title']
30 msg['Item-Identifier'] = f"{callbackMessage['feed_item']['identifier']}"
31 for link in callbackMessage['feed_item']['links']:
32 msg.add_header('Link', link['uri'])
33 date = None
34 if 'date' in callbackMessage['feed_item']:
35 date = isoparse(callbackMessage['feed_item']['date'])
36 else:
37 date = datetime.now(tzlocal())
38 msg['Date'] = date.strftime('%a, %e %b %Y %T %z')
39
40 if 'content' in callbackMessage['feed_item'] and callbackMessage['feed_item']['content']:
41 msg.set_content(html2text(callbackMessage['feed_item']['content']))
42 msg.add_alternative(callbackMessage['feed_item']['content'], subtype='html')
43
44
45 subprocess.run(
46 args=['notmuch', 'insert'],
47 check=True,
48 input=bytes(msg)
49 )
50
51if __name__ == '__main__':
52 sys.exit(main())
diff --git a/user-profiles/feeds/module.nix b/user-profiles/feeds/module.nix
deleted file mode 100644
index 63e827eb..00000000
--- a/user-profiles/feeds/module.nix
+++ /dev/null
@@ -1,236 +0,0 @@
1{ config, flakeInputs, pkgs, lib, system, ... }:
2
3with lib;
4
5let
6 inherit (flakeInputs.home-manager.lib) hm;
7
8 databasePath = "${config.xdg.dataHome}/feeds";
9
10 imm =
11 let
12 hlib = pkgs.haskell.lib;
13 haskellPackages = pkgs.haskellPackages.override {
14 overrides = finalHaskell: prevHaskell: {
15 uri-bytestring = finalHaskell.callCabal2nix "uri-bytestring" (pkgs.fetchFromGitHub {
16 owner = "gkleen";
17 repo = "uri-bytestring";
18 rev = "5f7f32c8274bc4d1b81d99582f5148fe3e8b637e";
19 sha256 = "XLanwyCDIlMuOkpE5LbTNOBfL+1kZX+URfj9Bhs1Nsc=";
20 fetchSubmodules = true;
21 }) {};
22 atom-conduit = finalHaskell.callCabal2nix "atom-conduit" (pkgs.fetchFromGitHub {
23 owner = "gkleen";
24 repo = "atom-conduit";
25 rev = "022f0182a02373f87c06a0a09817c8c41efe2425";
26 sha256 = "8yEyh3ymqkoM/YP+eBqPq1I5ofzj0Qn7ojL7IWx1DPo=";
27 fetchSubmodules = true;
28 }) {};
29 rss-conduit = finalHaskell.callCabal2nix "rss-condit" (pkgs.fetchFromGitHub {
30 owner = "gkleen";
31 repo = "rss-conduit";
32 rev = "dbb0960a8d3dc519f1607aa0223b3a25a49282ef";
33 sha256 = "Md1XApZWkdv4JvNoaVnjz0S85LbEC6w9U3PUcwXfu94=";
34 fetchSubmodules = true;
35 }) {};
36 beam-core = hlib.doJailbreak (finalHaskell.callCabal2nix "beam-core" "${beamSrc}/beam-core" {});
37 beam-migrate = hlib.doJailbreak (finalHaskell.callCabal2nix "beam-migrate" "${beamSrc}/beam-migrate" {});
38 beam-sqlite = hlib.doJailbreak (finalHaskell.callCabal2nix "beam-sqlite" "${beamSrc}/beam-sqlite" {});
39
40 imm = finalHaskell.callCabal2nix "imm" (pkgs.fetchFromGitHub {
41 owner = "k0ral";
42 repo = "imm";
43 rev = "5033879667264cb44cee65671a66f6aa43f249e7";
44 sha256 = "PG22caLQmAGhLZP49HsazuNd8IFKKaTuhXIQBD8v4Fs=";
45 fetchSubmodules = true;
46 }) {};
47 };
48 };
49 beamSrc = pkgs.fetchFromGitHub {
50 owner = "haskell-beam";
51 repo = "beam";
52 rev = "efd464b079755a781c2bb7a2fc030d6c141bbb8a";
53 sha256 = "8nTuBP/vD0L/qMo4h3XNrGZvpIwXuMVdj40j5gvHU6w=";
54 fetchSubmodules = true;
55 };
56 in haskellPackages.imm;
57 immWrapped = pkgs.runCommand "${imm.name}-wrapped-${config.home.username}"
58 { nativeBuildInputs = with pkgs; [ makeWrapper ];
59 } ''
60 mkdir -p $out/bin
61 makeWrapper ${imm}/bin/imm $out/bin/imm \
62 --add-flags --callbacks=${notmuchCallbacks}
63 '';
64
65 notmuchCallbacks = pkgs.writeText "imm-callbacks-${config.home.username}.dhall" ''
66 [ { _executable = "${immNotmuchInsert}/bin/imm-notmuch-insert"
67 , _arguments = [] : List Text
68 }
69 ]
70 '';
71
72 immNotmuchInsert = pkgs.stdenv.mkDerivation rec {
73 name = "imm-notmuch-insert-${config.home.username}";
74 src = ./imm-notmuch-insert.py;
75
76 phases = [ "buildPhase" "checkPhase" "installPhase" "fixupPhase" ];
77
78 python = pkgs.python39.withPackages (ps: with ps; [ configparser python-dateutil html2text ]);
79
80 nativeBuildInputs = with pkgs; [ makeWrapper ];
81
82 buildPhase = ''
83 substituteAll $src imm-notmuch-insert
84 '';
85
86 doCheck = true;
87 checkPhase = ''
88 ${python}/bin/python -m py_compile imm-notmuch-insert
89 '';
90
91 installPhase = ''
92 install -m 0755 -D -t $out/bin \
93 imm-notmuch-insert
94 '';
95
96 fixupPhase = ''
97 wrapProgram $out/bin/imm-notmuch-insert \
98 --prefix PATH : ${pkgs.notmuch}/bin \
99 --set NOTMUCH_CONFIG ${configPath}
100 '';
101 };
102
103 mkIniKeyValue = key: value:
104 let
105 tweakVal = v:
106 if isString v then
107 v
108 else if isList v then
109 concatMapStringsSep ";" tweakVal v
110 else if isBool v then
111 (if v then "true" else "false")
112 else
113 toString v;
114 in "${key}=${tweakVal value}";
115
116 notmuchIni = {
117 database = { path = databasePath; };
118
119 maildir = { synchronize_flags = false; };
120
121 new = {
122 ignore = [];
123 tags = ["new"];
124 };
125
126 user = {
127 name = config.home.username;
128 primary_email = "${config.home.username}@imm.invalid";
129 };
130
131 search = { exclude_tags = ["deleted"]; };
132 };
133 configPath = pkgs.writeText "notmuchrc" (generators.toINI { mkKeyValue = mkIniKeyValue; } notmuchIni);
134
135 afewConfigDir = pkgs.symlinkJoin {
136 name = "afew-config";
137 paths = [
138 (pkgs.writeTextDir "config" ''
139 [InboxFilter]
140 '')
141 ];
142 };
143
144 notmuchHooksDir =
145 let
146 afewHook = pkgs.writeShellScript "afew" ''
147 exec -- ${pkgs.afew}/bin/afew -c ${afewConfigDir} -C ${configPath} --tag --new -vv
148 '';
149 in pkgs.linkFarm "notmuch-hooks" [
150 { name = "post-new";
151 path = afewHook;
152 }
153 { name = "post-insert";
154 path = afewHook;
155 }
156 ];
157
158 notmuchWrapped = pkgs.runCommand "${pkgs.notmuch.name}-wrapped-${config.home.username}"
159 { nativeBuildInputs = with pkgs; [ makeWrapper ];
160 } ''
161 mkdir -p $out/bin
162 makeWrapper ${pkgs.notmuch}/bin/notmuch $out/bin/notmuch-feeds \
163 --set NOTMUCH_CONFIG ${configPath}
164 '';
165 alotWrapped = pkgs.runCommand "${pkgs.alot.name}-wrapped-${config.home.username}"
166 { nativeBuildInputs = with pkgs; [ makeWrapper gnused ];
167 } ''
168 mkdir -p $out/bin
169 makeWrapper ${pkgs.alot}/bin/alot $out/bin/alot-feeds \
170 --prefix MAILCAPS : ${alotMailcaps} \
171 --add-flags --config=${alotConfig} \
172 --add-flags --notmuch-config=${configPath}
173
174 mkdir $out/share
175 ln -s ${pkgs.alot}/share/alot $out/share
176 mkdir -p $out/share/applications
177 sed -r 's/alot/alot-feeds/g' ${pkgs.alot}/share/applications/alot.desktop > $out/share/applications/alot-feeds.desktop
178 mkdir -p $out/share/zsh/site-functions
179 sed -r 's/alot/alot-feeds/g' ${pkgs.alot}/share/zsh/site-functions/_alot > $out/share/zsh/site-functions/_alot-feeds
180 '';
181
182 alotConfig = pkgs.runCommand "alot" {
183 realname = notmuchIni.user.name;
184 address = notmuchIni.user.primary_email;
185 } "substituteAll ${./alot.config} $out";
186 alotMailcaps = pkgs.writeText "mailcaps" ''
187 text/html; ${pkgs.lynx}/bin/lynx -dump -dont_wrap_pre -assume_charset=utf-8 -display_charset=utf-8 "%s"; nametemplate=%s.html; copiousoutput
188 '';
189in {
190 config = {
191 home.packages = [ immWrapped notmuchWrapped pkgs.notmuch.man alotWrapped ];
192
193 home.activation.createImm = hm.dag.entryAfter ["writeBoundary"] ''
194 $DRY_RUN_CMD mkdir -p $VERBOSE_ARG ${config.xdg.configHome}/imm
195 '';
196
197 home.activation.createFeedsDatabase = hm.dag.entryAfter ["linkGeneration" "writeBoundary"] ''
198 $DRY_RUN_CMD mkdir -p -m 0750 $VERBOSE_ARG ${databasePath}
199 $DRY_RUN_CMD mkdir -p $VERBOSE_ARG ${databasePath}/new ${databasePath}/cur ${databasePath}/tmp
200 if ! [[ -d ${databasePath}/.notmuch ]]; then
201 NOTMUCH_VERBOSE_ARG="--quiet"
202 if [[ -v VERBOSE ]]; then
203 NOTMUCH_VERBOSE_ARG="--verbose"
204 fi
205 NOTMUCH_CONFIG=${configPath} $DRY_RUN_CMD ${pkgs.notmuch}/bin/notmuch new $NOTMUCH_VERBOSE_ARG
206 fi
207 $DRY_RUN_CMD ln -Tsf $VERBOSE_ARG ${notmuchHooksDir} ${databasePath}/.notmuch/hooks
208 '';
209
210 systemd.user.services."logrotate-imm" = {
211 Unit = {
212 Description = "Rotate imm logfile";
213 };
214 Service = {
215 Type = "oneshot";
216 ExecStart = ''
217 ${pkgs.logrotate}/bin/logrotate --state ${config.xdg.configHome}/imm/imm.logrotate ${pkgs.writeText "logrotate.conf" ''
218 ${config.xdg.configHome}/imm/imm.log {
219 rotate 5
220 size 1024k
221 }
222 ''}
223 '';
224 };
225 };
226 systemd.user.timers."logrotate-imm" = {
227 Timer = {
228 OnActiveSec = "6h";
229 OnUnitActiveSec = "6h";
230 };
231 Install = {
232 WantedBy = ["default.target"];
233 };
234 };
235 };
236}
diff --git a/user-profiles/mpv/default.nix b/user-profiles/mpv/default.nix
index 83eba2a9..94f241c8 100644
--- a/user-profiles/mpv/default.nix
+++ b/user-profiles/mpv/default.nix
@@ -10,7 +10,7 @@
10 (pkgs.stdenv.mkDerivation (sources.mpv-reload // rec { 10 (pkgs.stdenv.mkDerivation (sources.mpv-reload // rec {
11 installPhase = '' 11 installPhase = ''
12 install -d $out/share/mpv/scripts 12 install -d $out/share/mpv/scripts
13 install -m 0644 reload.lua $out/share/mpv/scripts/${passthru.scriptName} 13 install -m 0644 main.lua $out/share/mpv/scripts/${passthru.scriptName}
14 ''; 14 '';
15 15
16 passthru.scriptName = "reload.lua"; 16 passthru.scriptName = "reload.lua";
diff --git a/user-profiles/tmux/default.nix b/user-profiles/tmux/default.nix
index 11c53788..7ea0c0d5 100644
--- a/user-profiles/tmux/default.nix
+++ b/user-profiles/tmux/default.nix
@@ -1,10 +1,11 @@
1{ userName, pkgs, lib, ... }: 1{ userName, pkgs, lib, ... }:
2{ 2{
3 home-manager.users.${userName} = { 3 home-manager.users.${userName} = { config, ... }: {
4 programs.tmux = { 4 programs.tmux = {
5 enable = true; 5 enable = true;
6 clock24 = true; 6 clock24 = true;
7 historyLimit = 50000; 7 historyLimit = 50000;
8 mouse = true;
8 extraConfig = lib.readFile (pkgs.stdenv.mkDerivation { 9 extraConfig = lib.readFile (pkgs.stdenv.mkDerivation {
9 name = "tmux.conf"; 10 name = "tmux.conf";
10 src = ./tmux.conf; 11 src = ./tmux.conf;
@@ -13,11 +14,10 @@
13 14
14 phases = [ "installPhase" ]; 15 phases = [ "installPhase" ];
15 16
16 inherit (pkgs) zsh;
17 mandb = pkgs.man-db;
18
19 installPhase = '' 17 installPhase = ''
20 substituteAll $src $out 18 substitute $src $out \
19 --subst-var-by zsh ${lib.getExe config.programs.zsh.package} \
20 --subst-var-by man ${lib.getExe config.programs.man.package}
21 ''; 21 '';
22 }); 22 });
23 }; 23 };
diff --git a/user-profiles/tmux/tmux.conf b/user-profiles/tmux/tmux.conf
index 415d13e7..9e658800 100644
--- a/user-profiles/tmux/tmux.conf
+++ b/user-profiles/tmux/tmux.conf
@@ -1,23 +1,20 @@
1set-option -g history-limit 50000
2set-option -g status-bg black 1set-option -g status-bg black
3set-option -g status-fg white 2set-option -g status-fg white
4set-option -g clock-mode-colour white 3set-option -g clock-mode-colour white
5set-option -g clock-mode-style 24
6set-option -g bell-action any 4set-option -g bell-action any
7set-option -g default-shell @zsh@/bin/zsh 5set-option -g default-shell @zsh@
8set-option -g update-environment 'DISPLAY SSH_ASKPASS SSH_AUTH_SOCK SSH_AGENT_PID SSH_CONNECTION WINDOWID XAUTHORITY PROMPT_INFO PATH PGHOST PGLOG' 6set-option -g update-environment 'DISPLAY SSH_ASKPASS SSH_AUTH_SOCK SSH_AGENT_PID SSH_CONNECTION WINDOWID XAUTHORITY PROMPT_INFO PATH PGHOST PGLOG'
9set-option -g mouse on
10set-option -g set-clipboard on 7set-option -g set-clipboard on
11set-option -g terminal-overrides 'rxvt-uni*:XT:Ms=\E]52;%p1%s;%p2%s\007' 8set-option -g terminal-overrides 'rxvt-uni*:XT:Ms=\E]52;%p1%s;%p2%s\007'
12 9
13set-environment -g LESS " -R " 10set-environment -g LESS " -R "
14 11
15## determine if we should enable 256-colour support 12## determine if we should enable 256-colour support
16if "[[ ''${TERM} =~ 256color || ''${TERM} == fbterm || ''${TERM} =~ alacritty ]]" 'set -g default-terminal tmux-256color' 13if "[[ ''${TERM} =~ 256color || ''${TERM} == fbterm || ''${TERM} =~ alacritty || ''${TERM} =~ kitty ]]" 'set -g default-terminal tmux-256color'
17 14
18set-option -g status-right "" 15set-option -g status-right ""
19 16
20bind / command-prompt "split-window -h 'exec @mandb@/bin/man %%'" 17bind / command-prompt "split-window -h 'exec @man@ %%'"
21bind C clock-mode 18bind C clock-mode
22bind r switch-client -r 19bind r switch-client -r
23 20
diff --git a/user-profiles/utils.nix b/user-profiles/utils.nix
index 13eb6033..da79e336 100644
--- a/user-profiles/utils.nix
+++ b/user-profiles/utils.nix
@@ -1,19 +1,6 @@
1{ userName, lib, pkgs, config, ... }: 1{ userName, lib, pkgs, config, ... }:
2let 2let
3 cfg = config.home-manager.users.${userName}; 3 cfg = config.home-manager.users.${userName};
4
5 wrappedLess = pkgs.less.overrideAttrs (oldAttrs: {
6 pname = "${oldAttrs.pname or "less"}-wrapper";
7
8 nativeBuildInputs = (oldAttrs.nativeBuildInputs or []) ++ (with pkgs; [makeWrapper]);
9
10 postInstall = ''
11 ${oldAttrs.postInstall or ""}
12
13 wrapProgram $out/bin/less \
14 --prefix PATH : ${lib.makeBinPath (with pkgs; [binutils])}
15 '';
16 });
17in { 4in {
18 home-manager.users.${userName} = { 5 home-manager.users.${userName} = {
19 programs = { 6 programs = {
@@ -55,19 +42,23 @@ in {
55 }; 42 };
56 43
57 jq.enable = true; 44 jq.enable = true;
45
46 lesspipe.enable = true;
47
48 man.enable = true;
58 }; 49 };
59 50
60 home.sessionVariables = { 51 home.sessionVariables = {
61 LESSCOLORIZER = "pygmentize -O style=rrt"; 52 LESSCOLORIZER = "${lib.getExe' pkgs.python3Packages.pygments "pygmentize"} -O style=rrt";
62 }; 53 };
63 54
64 home.packages = with pkgs; [ 55 home.packages = with pkgs; [
65 autossh usbutils pciutils eza silver-searcher pwgen xkcdpass 56 autossh usbutils pciutils eza silver-searcher pwgen xkcdpass
66 unzip magic-wormhole qrencode tty-clock dnsutils openssl sshfs 57 unzip magic-wormhole dnsutils openssl sshfs
67 psmisc mosh tree vnstat file pv bc zip nmap aspell 58 psmisc mosh tree vnstat file pv bc zip nmap aspell
68 aspellDicts.de aspellDicts.en borgbackup man-pages rsync socat 59 aspellDicts.de aspellDicts.en borgbackup man-pages rsync socat
69 inetutils yq cached-nix-shell persistent-nix-shell rage 60 inetutils yq cached-nix-shell persistent-nix-shell rage
70 smartmontools hdparm nix-output-monitor wrappedLess dscp 61 smartmontools hdparm nix-output-monitor less dscp
71 iputils 62 iputils
72 ]; 63 ];
73 }; 64 };
diff --git a/user-profiles/yt-dlp.nix b/user-profiles/yt-dlp.nix
index 9e77f734..5c9858a6 100644
--- a/user-profiles/yt-dlp.nix
+++ b/user-profiles/yt-dlp.nix
@@ -13,11 +13,12 @@
13 "best" 13 "best"
14 ]; 14 ];
15 embed-subs = true; 15 embed-subs = true;
16 embed-thumbnail = true;
17 embed-metadata = true;
16 # write-subs = true; 18 # write-subs = true;
17 write-auto-subs = true; 19 write-auto-subs = true;
18 sub-langs = "en(-(gb|us|orig))?,de(-(de|orig))?,-live_chat,-rechat"; 20 sub-langs = "en(-(gb|us|orig))?,de(-(de|orig))?,-live_chat,-rechat";
19 prefer-free-formats = true; 21 prefer-free-formats = true;
20 embed-metadata = true;
21 # downloader = "${pkgs.axel}/bin/axel"; 22 # downloader = "${pkgs.axel}/bin/axel";
22 concurrent-fragments = 12; 23 concurrent-fragments = 12;
23 buffer-size = "16K"; 24 buffer-size = "16K";
@@ -28,7 +29,8 @@
28 # "youtube:formats=dashy" 29 # "youtube:formats=dashy"
29 # ]; 30 # ];
30 remux-video = "mp4>mkv"; 31 remux-video = "mp4>mkv";
31 output = "\"%(title)s [%(uploader)s %(webpage_url)s].%(ext)s\""; 32 output = lib.mkDefault "\"%(modified_date>%Y%m%d,release_date>%Y%m%d,upload_date>%Y%m%d)s %(title)s [%(uploader)s %(webpage_url)s].%(ext)s\"";
33 audio-multistreams = true;
32 }; 34 };
33 }; 35 };
34 }; 36 };
diff --git a/user-profiles/zsh/default.nix b/user-profiles/zsh/default.nix
index daeb7e82..ab523a52 100644
--- a/user-profiles/zsh/default.nix
+++ b/user-profiles/zsh/default.nix
@@ -1,38 +1,72 @@
1{ userName, pkgs, customUtils, lib, config, ... }: 1{ userName, pkgs, customUtils, lib, config, ... }:
2let 2{
3 dotDir = ".config/zsh"; 3 config = {
4 p10kZsh = "${dotDir}/.p10k.zsh"; 4 home-manager.users.${userName} = let sysConfig = config; in { config, ... }: {
5 cfg = config.home-manager.users.${userName}; 5 config = {
6in { 6 programs.zsh = {
7 home-manager.users.${userName} = { 7 dotDir = ".config/zsh";
8 programs.zsh = { 8 enable = true;
9 inherit dotDir; 9 autocd = true;
10 enable = true; 10 enableCompletion = true;
11 autocd = true; 11 enableVteIntegration = true;
12 enableCompletion = true; 12 history = {
13 13 append = true;
14 plugins = [ 14 expireDuplicatesFirst = true;
15 { name = "powerlevel10k"; 15 extended = true;
16 file = "share/zsh-powerlevel10k/powerlevel10k.zsh-theme"; 16 findNoDups = true;
17 src = pkgs.zsh-powerlevel10k; 17 };
18 } 18 syntaxHighlighting.enable = true;
19 ]; 19 zsh-abbr = {
20 initExtraFirst = '' 20 enable = true;
21 if [[ $TERM == "dumb" ]]; then 21 abbreviations = {
22 unsetopt zle 22 re = "systemctl restart";
23 PS1='$ ' 23 ure = "systemctl --user restart";
24 return 24 st = "systemctl status";
25 fi 25 ust = "systemctl --user status";
26 ''; 26 };
27 initExtraBeforeCompInit = '' 27 globalAbbreviations = {
28 source "${cfg.home.homeDirectory}/${p10kZsh}" 28 "L" = "| less";
29 ''; 29 "S" = "&> /dev/null";
30 initExtra = lib.mkAfter '' 30 "G" = "| grep";
31 source ${./zshrc} 31 "B" = "&> /dev/null &";
32 source "${pkgs.zsh-syntax-highlighting}/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" 32 "BB" = "&> /dev/null &!";
33 ''; 33 "J" = lib.mkIf config.programs.jq.enable "| jq '.'";
34 };
35 };
36
37 plugins = [
38 { name = "powerlevel10k";
39 file = "share/zsh-powerlevel10k/powerlevel10k.zsh-theme";
40 src = pkgs.zsh-powerlevel10k;
41 }
42 ];
43 initContent = lib.mkMerge [
44 (lib.mkBefore ''
45 if [[ $TERM == "dumb" ]]; then
46 unsetopt zle
47 PS1='$ '
48 return
49 fi
50 '')
51 (lib.mkOrder 550 ''
52 source "$HOME/${config.xdg.configFile."zsh/.p10k.zsh".target}"
53 '')
54 (lib.mkAfter ''
55 source ${./zshrc}
56 '')
57 ];
58 };
59
60 xdg.configFile."zsh/.p10k.zsh".source = ./p10k.zsh;
61 };
34 }; 62 };
35 63
36 home.file.${p10kZsh}.source = ./p10k.zsh; 64 programs.zsh.enable = true;
65 environment.pathsToLink = [ "/share/zsh" ];
66 environment.shellAliases = lib.mkOverride 90 {};
67
68 nixpkgs.externalConfig.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
69 "zsh-abbr"
70 ];
37 }; 71 };
38} 72}
diff --git a/user-profiles/zsh/zshrc b/user-profiles/zsh/zshrc
index a83a8069..af3aca64 100644
--- a/user-profiles/zsh/zshrc
+++ b/user-profiles/zsh/zshrc
@@ -24,7 +24,12 @@ setopt ignore_eof
24bindkey -e 24bindkey -e
25bindkey ';5C' emacs-forward-word 25bindkey ';5C' emacs-forward-word
26bindkey ';5D' emacs-backward-word 26bindkey ';5D' emacs-backward-word
27bindkey '^[[1;5C' emacs-forward-word
28bindkey '^[[1;5D' emacs-backward-word
29bindkey '^H' backward-kill-word
27 30
28autoload -Uz url-quote-magic bracketed-paste-magic 31autoload -Uz url-quote-magic bracketed-paste-magic
29zle -N self-insert url-quote-magic 32zle -N self-insert url-quote-magic
30zle -N bracketed-paste bracketed-paste-magic \ No newline at end of file 33zle -N bracketed-paste bracketed-paste-magic
34
35setopt extended_glob
diff --git a/users/gkleen/default.nix b/users/gkleen/default.nix
index 4ddf4be3..5ce93de7 100644
--- a/users/gkleen/default.nix
+++ b/users/gkleen/default.nix
@@ -1,7 +1,7 @@
1{ flake, userName, pkgs, customUtils, lib, ... }: 1{ flake, userName, pkgs, customUtils, lib, ... }:
2{ 2{
3 imports = with flake.nixosModules.userProfiles.${userName}; [ 3 imports = with flake.nixosModules.userProfiles.${userName}; [
4 zsh tmux utils direnv 4 utils direnv
5 ]; 5 ];
6 6
7 users.users.${userName} = { 7 users.users.${userName} = {
@@ -29,9 +29,39 @@
29 userName = "Gregor Kleen"; 29 userName = "Gregor Kleen";
30 delta.enable = true; 30 delta.enable = true;
31 extraConfig = { 31 extraConfig = {
32 pull.rebase = false; 32 core.excludesfile = toString ./gitignore;
33 pull.rebase = true;
33 submodule.recurse = true; 34 submodule.recurse = true;
34 init.defaultBranch = "main"; 35 init.defaultBranch = "main";
36 column.ui = "auto";
37 branch.sort = "-committerdate";
38 tag.sort = "version:refname";
39 diff = {
40 algorithm = "histogram";
41 colorMoved = "plain";
42 mnemonicPrefix = true;
43 renames = true;
44 };
45 push = {
46 default = "simple";
47 autoSetupRemote = true;
48 followTags = true;
49 };
50 fetch = {
51 prune = true;
52 pruneTags = true;
53 all = true;
54 };
55 rerere = {
56 enabled = true;
57 autoupdate = true;
58 };
59 rebase = {
60 autoSquash = true;
61 autoStash = true;
62 updateRefs = true;
63 };
64 merge.conflictstyle = "zdiff3";
35 }; 65 };
36 }; 66 };
37 67
diff --git a/users/gkleen/gitignore b/users/gkleen/gitignore
new file mode 100644
index 00000000..f7082b20
--- /dev/null
+++ b/users/gkleen/gitignore
@@ -0,0 +1,2 @@
1**/#*#
2**/.#*
diff --git a/users/root.nix b/users/root.nix
index b61f9cfd..ed1acd50 100644
--- a/users/root.nix
+++ b/users/root.nix
@@ -3,7 +3,7 @@ let
3 haveGKleen = flake.nixosModules.accounts ? "gkleen@${hostName}"; 3 haveGKleen = flake.nixosModules.accounts ? "gkleen@${hostName}";
4in { 4in {
5 imports = with flake.nixosModules.userProfiles.${userName}; [ 5 imports = with flake.nixosModules.userProfiles.${userName}; [
6 zsh tmux direnv utils 6 direnv utils
7 ]; 7 ];
8 8
9 users.users.${userName} = lib.mkIf haveGKleen { 9 users.users.${userName} = lib.mkIf haveGKleen {