summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.sops.yaml6
-rw-r--r--_sources/generated.json62
-rw-r--r--_sources/generated.nix62
-rw-r--r--accounts/gkleen@sif/default.nix86
-rw-r--r--accounts/gkleen@sif/emacs.el2
-rw-r--r--accounts/gkleen@sif/niri/default.nix271
-rw-r--r--accounts/gkleen@sif/niri/mako.nix4
-rw-r--r--accounts/gkleen@sif/niri/waybar.nix21
-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.nix2
-rw-r--r--accounts/gkleen@sif/utils/pdf2pdf.nix8
-rw-r--r--accounts/gkleen@sif/zshrc8
-rw-r--r--flake.lock413
-rw-r--r--flake.nix31
-rw-r--r--home-modules/nixpkgs-release-check.nix4
-rw-r--r--hosts/eostre/default.nix21
-rw-r--r--hosts/sif/default.nix78
-rw-r--r--hosts/sif/email/default.nix111
-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/hw.nix2
-rw-r--r--hosts/sif/mail/default.nix70
-rw-r--r--hosts/surtr/bifrost/default.nix4
-rw-r--r--hosts/surtr/default.nix3
-rw-r--r--hosts/surtr/dns/default.nix2
-rw-r--r--hosts/surtr/dns/keys/kimai.yggdrasil.li_acme19
-rw-r--r--hosts/surtr/dns/zones/email.nights.soa8
-rw-r--r--hosts/surtr/dns/zones/li.141.soa9
-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.yggdrasil.soa12
-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.nix217
-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/kimai.nix68
-rw-r--r--hosts/surtr/postgresql/default.nix58
-rw-r--r--hosts/surtr/tls/tsig_keys/kimai.yggdrasil.li19
-rw-r--r--hosts/vidhar/audiobookshelf/abs-podcast-autoplaylist-gkleen.toml6
-rw-r--r--hosts/vidhar/default.nix4
-rw-r--r--hosts/vidhar/kimai/default.nix89
-rw-r--r--hosts/vidhar/kimai/ruleset.nft149
-rw-r--r--hosts/vidhar/network/ruleset.nft12
-rw-r--r--hosts/vidhar/prometheus/default.nix1
-rw-r--r--lib/pythonSet.nix42
-rw-r--r--modules/abs-podcast-autoplaylist.nix1
-rw-r--r--modules/borgcopy/.envrc4
-rw-r--r--modules/borgcopy/.gitignore2
-rw-r--r--modules/borgcopy/default.nix39
-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/pgbackrest.nix2
-rw-r--r--modules/postsrsd.nix6
-rw-r--r--modules/systemd-run0.nix4
-rw-r--r--modules/uucp.nix373
-rw-r--r--overlays/abs-podcast-autoplaylist/default.nix25
-rw-r--r--overlays/deploy-rs.nix10
-rw-r--r--overlays/etesync-dav.nix49
-rw-r--r--overlays/prometheus-lvm-exporter.nix2
-rw-r--r--overlays/spice-record.nix2
-rw-r--r--overlays/swayosd/default.nix2
-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/worktime/.envrc4
-rw-r--r--overlays/worktime/.gitignore2
-rw-r--r--overlays/worktime/default.nix26
-rw-r--r--overlays/worktime/poetry.lock284
-rw-r--r--overlays/worktime/pyproject.toml41
-rw-r--r--overlays/worktime/uv.lock248
-rwxr-xr-xoverlays/worktime/worktime/__main__.py487
-rw-r--r--shell.nix10
-rw-r--r--system-profiles/bcachefs.nix2
-rw-r--r--system-profiles/core/default.nix8
-rw-r--r--system-profiles/lanzaboote.nix14
-rw-r--r--system-profiles/nfsroot.nix4
-rw-r--r--system-profiles/zfs.nix2
-rw-r--r--user-profiles/tmux/default.nix4
-rw-r--r--user-profiles/utils.nix2
-rw-r--r--user-profiles/yt-dlp.nix4
94 files changed, 2918 insertions, 1714 deletions
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 be1e12e9..62c68113 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-03-06", 25 "date": "2025-08-18",
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": "64e7da048b14822bef06f3971189c4c0985422e7", 37 "rev": "f19bc1a9402b6fb014e3b7114f06ffba5abdf5cc",
38 "sha256": "sha256-cyyRNvU35ujxkLraOqw2oiZwUblBpJaEncPl2++VHL4=", 38 "sha256": "sha256-syYoC3XOJTUaL/Db0T10mSUak83qAl6Tx2fE6k4XLpI=",
39 "sparseCheckout": [], 39 "sparseCheckout": [],
40 "type": "github" 40 "type": "github"
41 }, 41 },
42 "version": "64e7da048b14822bef06f3971189c4c0985422e7" 42 "version": "f19bc1a9402b6fb014e3b7114f06ffba5abdf5cc"
43 }, 43 },
44 "emacs-scratch_el": { 44 "emacs-scratch_el": {
45 "cargoLocks": null, 45 "cargoLocks": null,
@@ -91,11 +91,11 @@
91 "passthru": null, 91 "passthru": null,
92 "pinned": false, 92 "pinned": false,
93 "src": { 93 "src": {
94 "sha256": "sha256-afJuTByGUMU6kFqGGa3pbPaFVdYGcJYiR0RfDNYNgDk=", 94 "sha256": "sha256-V+fB5KkbBRhVSDgB/e7oVEyMKQ7HbR82XQYlqxcLZyQ=",
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.19.tar.gz"
97 }, 97 },
98 "version": "2.17" 98 "version": "2.19"
99 }, 99 },
100 "mako": { 100 "mako": {
101 "cargoLocks": null, 101 "cargoLocks": null,
@@ -270,11 +270,11 @@
270 "pinned": false, 270 "pinned": false,
271 "src": { 271 "src": {
272 "name": null, 272 "name": null,
273 "sha256": "sha256-8kd17ChqLuVH5/OdPc2rVDKEDWHl9ZWLh8k+EBrCGH8=", 273 "sha256": "sha256-ipbZJ0mPCuwzb/TDtXXUBTuWOcSsKGAJ1GEGIgB2G7E=",
274 "type": "url", 274 "type": "url",
275 "url": "https://github.com/netbootxyz/netboot.xyz/releases/download/2.0.87/netboot.xyz.efi" 275 "url": "https://github.com/netbootxyz/netboot.xyz/releases/download/2.0.88/netboot.xyz.efi"
276 }, 276 },
277 "version": "2.0.87" 277 "version": "2.0.88"
278 }, 278 },
279 "netbootxyz-lkrn": { 279 "netbootxyz-lkrn": {
280 "cargoLocks": null, 280 "cargoLocks": null,
@@ -285,11 +285,11 @@
285 "pinned": false, 285 "pinned": false,
286 "src": { 286 "src": {
287 "name": null, 287 "name": null,
288 "sha256": "sha256-/qY3NdRC0SghQ4kamrkm9EFumrKlirqDCJ+XY+jHWLA=", 288 "sha256": "sha256-igy3O30noS25dU7ZnHuKrWqLLkjjd/L46IdCTd038dI=",
289 "type": "url", 289 "type": "url",
290 "url": "https://github.com/netbootxyz/netboot.xyz/releases/download/2.0.87/netboot.xyz.lkrn" 290 "url": "https://github.com/netbootxyz/netboot.xyz/releases/download/2.0.88/netboot.xyz.lkrn"
291 }, 291 },
292 "version": "2.0.87" 292 "version": "2.0.88"
293 }, 293 },
294 "postfix-mta-sts-resolver": { 294 "postfix-mta-sts-resolver": {
295 "cargoLocks": null, 295 "cargoLocks": null,
@@ -327,11 +327,11 @@
327 "passthru": null, 327 "passthru": null,
328 "pinned": false, 328 "pinned": false,
329 "src": { 329 "src": {
330 "sha256": "sha256-Nz/lBhQbzWSnOKN4n0OUdJzDTpf3mfY0+FXoCqF03TU=", 330 "sha256": "sha256-JDKt+MzxxyaFWnzuq/7FfT/JPUknH/RRw4Cb8XDOtlk=",
331 "type": "tarball", 331 "type": "tarball",
332 "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"
333 }, 333 },
334 "version": "0.4.0" 334 "version": "0.6.0"
335 }, 335 },
336 "psql-versioning": { 336 "psql-versioning": {
337 "cargoLocks": null, 337 "cargoLocks": null,
@@ -397,7 +397,7 @@
397 }, 397 },
398 "swayosd": { 398 "swayosd": {
399 "cargoLocks": null, 399 "cargoLocks": null,
400 "date": "2025-04-20", 400 "date": "2025-07-07",
401 "extract": null, 401 "extract": null,
402 "name": "swayosd", 402 "name": "swayosd",
403 "passthru": null, 403 "passthru": null,
@@ -407,13 +407,13 @@
407 "fetchSubmodules": false, 407 "fetchSubmodules": false,
408 "leaveDotGit": false, 408 "leaveDotGit": false,
409 "name": null, 409 "name": null,
410 "rev": "ce1f34d80a7f8b4393a5551ea0535bd8beabb28c", 410 "rev": "73aed75146b81aaf67c4301353790ff5a17aed1f",
411 "sha256": "sha256-Z9c/5jKxs5ctUuVu7g+BXA1Wy4lyZLpGATtj2jd84jI=", 411 "sha256": "sha256-p31HNelptAw7Sk0NmYP4FkoUCdA5uAsrXC20JJp24Vw=",
412 "sparseCheckout": [], 412 "sparseCheckout": [],
413 "type": "git", 413 "type": "git",
414 "url": "https://github.com/ErikReider/SwayOSD" 414 "url": "https://github.com/ErikReider/SwayOSD"
415 }, 415 },
416 "version": "ce1f34d80a7f8b4393a5551ea0535bd8beabb28c" 416 "version": "73aed75146b81aaf67c4301353790ff5a17aed1f"
417 }, 417 },
418 "tomorrow-night-paradise-theme": { 418 "tomorrow-night-paradise-theme": {
419 "cargoLocks": null, 419 "cargoLocks": null,
@@ -437,7 +437,7 @@
437 }, 437 },
438 "v4l2loopback": { 438 "v4l2loopback": {
439 "cargoLocks": null, 439 "cargoLocks": null,
440 "date": "2025-04-29", 440 "date": "2025-08-18",
441 "extract": null, 441 "extract": null,
442 "name": "v4l2loopback", 442 "name": "v4l2loopback",
443 "passthru": null, 443 "passthru": null,
@@ -449,16 +449,16 @@
449 "name": null, 449 "name": null,
450 "owner": "umlaeute", 450 "owner": "umlaeute",
451 "repo": "v4l2loopback", 451 "repo": "v4l2loopback",
452 "rev": "8d806ad688961d8840081a609c39d1a82d296b24", 452 "rev": "5eaa59e7c41d0e6f35a6c14c3b756d94d25f58ed",
453 "sha256": "sha256-zuE/qFI8QCWCePmHWjTIPTh2KzmDkwQ2uj5C1dAwo1c=", 453 "sha256": "sha256-YcSpNfItvUdPVirlDyGdYuCnVvxHhh780x+OI5VNZmE=",
454 "sparseCheckout": [], 454 "sparseCheckout": [],
455 "type": "github" 455 "type": "github"
456 }, 456 },
457 "version": "8d806ad688961d8840081a609c39d1a82d296b24" 457 "version": "5eaa59e7c41d0e6f35a6c14c3b756d94d25f58ed"
458 }, 458 },
459 "xcompose": { 459 "xcompose": {
460 "cargoLocks": null, 460 "cargoLocks": null,
461 "date": "2025-03-11", 461 "date": "2025-06-05",
462 "extract": null, 462 "extract": null,
463 "name": "xcompose", 463 "name": "xcompose",
464 "passthru": null, 464 "passthru": null,
@@ -470,12 +470,12 @@
470 "name": null, 470 "name": null,
471 "owner": "kragen", 471 "owner": "kragen",
472 "repo": "xcompose", 472 "repo": "xcompose",
473 "rev": "8b5a6a0c788fd0a4b921d9d3737174defb863873", 473 "rev": "4d8eab4d05a19537ce79294ae0459fdae78ffb20",
474 "sha256": "sha256-6EjQErdBOd5hqcrdaf88E1UZVYIc3FOfv34hvUwOWdA=", 474 "sha256": "sha256-vKY4u5Z2IL111orLLkkF4AoVzqluKG/VQhNUUCqO/k8=",
475 "sparseCheckout": [], 475 "sparseCheckout": [],
476 "type": "github" 476 "type": "github"
477 }, 477 },
478 "version": "8b5a6a0c788fd0a4b921d9d3737174defb863873" 478 "version": "4d8eab4d05a19537ce79294ae0459fdae78ffb20"
479 }, 479 },
480 "yt-dlp": { 480 "yt-dlp": {
481 "cargoLocks": null, 481 "cargoLocks": null,
@@ -486,10 +486,10 @@
486 "pinned": false, 486 "pinned": false,
487 "src": { 487 "src": {
488 "name": null, 488 "name": null,
489 "sha256": "sha256-0BNn0MOulONcseLsy3p8cOGBxMpEj07iN08mSJ0mNgM=", 489 "sha256": "sha256-2oc7z0JBd6tcO3AfqU6kzawXvzrsXvN7kfUwyQ3ve88=",
490 "type": "url", 490 "type": "url",
491 "url": "https://pypi.org/packages/source/y/yt_dlp/yt_dlp-2025.4.30.tar.gz" 491 "url": "https://pypi.org/packages/source/y/yt_dlp/yt_dlp-2025.8.20.tar.gz"
492 }, 492 },
493 "version": "2025.4.30" 493 "version": "2025.8.20"
494 } 494 }
495} \ No newline at end of file 495} \ No newline at end of file
diff --git a/_sources/generated.nix b/_sources/generated.nix
index ff85bc0d..4368c98a 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 = "64e7da048b14822bef06f3971189c4c0985422e7"; 21 version = "f19bc1a9402b6fb014e3b7114f06ffba5abdf5cc";
22 src = fetchFromGitHub { 22 src = fetchFromGitHub {
23 owner = "xdp-project"; 23 owner = "xdp-project";
24 repo = "bpf-examples"; 24 repo = "bpf-examples";
25 rev = "64e7da048b14822bef06f3971189c4c0985422e7"; 25 rev = "f19bc1a9402b6fb014e3b7114f06ffba5abdf5cc";
26 fetchSubmodules = true; 26 fetchSubmodules = true;
27 sha256 = "sha256-cyyRNvU35ujxkLraOqw2oiZwUblBpJaEncPl2++VHL4="; 27 sha256 = "sha256-syYoC3XOJTUaL/Db0T10mSUak83qAl6Tx2fE6k4XLpI=";
28 }; 28 };
29 date = "2025-03-06"; 29 date = "2025-08-18";
30 }; 30 };
31 emacs-scratch_el = { 31 emacs-scratch_el = {
32 pname = "emacs-scratch_el"; 32 pname = "emacs-scratch_el";
@@ -53,10 +53,10 @@
53 }; 53 };
54 lesspipe = { 54 lesspipe = {
55 pname = "lesspipe"; 55 pname = "lesspipe";
56 version = "2.17"; 56 version = "2.19";
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.19.tar.gz";
59 sha256 = "sha256-afJuTByGUMU6kFqGGa3pbPaFVdYGcJYiR0RfDNYNgDk="; 59 sha256 = "sha256-V+fB5KkbBRhVSDgB/e7oVEyMKQ7HbR82XQYlqxcLZyQ=";
60 }; 60 };
61 }; 61 };
62 mako = { 62 mako = {
@@ -164,18 +164,18 @@
164 }; 164 };
165 netbootxyz-efi = { 165 netbootxyz-efi = {
166 pname = "netbootxyz-efi"; 166 pname = "netbootxyz-efi";
167 version = "2.0.87"; 167 version = "2.0.88";
168 src = fetchurl { 168 src = fetchurl {
169 url = "https://github.com/netbootxyz/netboot.xyz/releases/download/2.0.87/netboot.xyz.efi"; 169 url = "https://github.com/netbootxyz/netboot.xyz/releases/download/2.0.88/netboot.xyz.efi";
170 sha256 = "sha256-8kd17ChqLuVH5/OdPc2rVDKEDWHl9ZWLh8k+EBrCGH8="; 170 sha256 = "sha256-ipbZJ0mPCuwzb/TDtXXUBTuWOcSsKGAJ1GEGIgB2G7E=";
171 }; 171 };
172 }; 172 };
173 netbootxyz-lkrn = { 173 netbootxyz-lkrn = {
174 pname = "netbootxyz-lkrn"; 174 pname = "netbootxyz-lkrn";
175 version = "2.0.87"; 175 version = "2.0.88";
176 src = fetchurl { 176 src = fetchurl {
177 url = "https://github.com/netbootxyz/netboot.xyz/releases/download/2.0.87/netboot.xyz.lkrn"; 177 url = "https://github.com/netbootxyz/netboot.xyz/releases/download/2.0.88/netboot.xyz.lkrn";
178 sha256 = "sha256-/qY3NdRC0SghQ4kamrkm9EFumrKlirqDCJ+XY+jHWLA="; 178 sha256 = "sha256-igy3O30noS25dU7ZnHuKrWqLLkjjd/L46IdCTd038dI=";
179 }; 179 };
180 }; 180 };
181 postfix-mta-sts-resolver = { 181 postfix-mta-sts-resolver = {
@@ -196,10 +196,10 @@
196 }; 196 };
197 prometheus-lvm-exporter = { 197 prometheus-lvm-exporter = {
198 pname = "prometheus-lvm-exporter"; 198 pname = "prometheus-lvm-exporter";
199 version = "0.4.0"; 199 version = "0.6.0";
200 src = fetchTarball { 200 src = fetchTarball {
201 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";
202 sha256 = "sha256-Nz/lBhQbzWSnOKN4n0OUdJzDTpf3mfY0+FXoCqF03TU="; 202 sha256 = "sha256-JDKt+MzxxyaFWnzuq/7FfT/JPUknH/RRw4Cb8XDOtlk=";
203 }; 203 };
204 }; 204 };
205 psql-versioning = { 205 psql-versioning = {
@@ -242,17 +242,17 @@
242 }; 242 };
243 swayosd = { 243 swayosd = {
244 pname = "swayosd"; 244 pname = "swayosd";
245 version = "ce1f34d80a7f8b4393a5551ea0535bd8beabb28c"; 245 version = "73aed75146b81aaf67c4301353790ff5a17aed1f";
246 src = fetchgit { 246 src = fetchgit {
247 url = "https://github.com/ErikReider/SwayOSD"; 247 url = "https://github.com/ErikReider/SwayOSD";
248 rev = "ce1f34d80a7f8b4393a5551ea0535bd8beabb28c"; 248 rev = "73aed75146b81aaf67c4301353790ff5a17aed1f";
249 fetchSubmodules = false; 249 fetchSubmodules = false;
250 deepClone = false; 250 deepClone = false;
251 leaveDotGit = false; 251 leaveDotGit = false;
252 sparseCheckout = [ ]; 252 sparseCheckout = [ ];
253 sha256 = "sha256-Z9c/5jKxs5ctUuVu7g+BXA1Wy4lyZLpGATtj2jd84jI="; 253 sha256 = "sha256-p31HNelptAw7Sk0NmYP4FkoUCdA5uAsrXC20JJp24Vw=";
254 }; 254 };
255 date = "2025-04-20"; 255 date = "2025-07-07";
256 }; 256 };
257 tomorrow-night-paradise-theme = { 257 tomorrow-night-paradise-theme = {
258 pname = "tomorrow-night-paradise-theme"; 258 pname = "tomorrow-night-paradise-theme";
@@ -270,34 +270,34 @@
270 }; 270 };
271 v4l2loopback = { 271 v4l2loopback = {
272 pname = "v4l2loopback"; 272 pname = "v4l2loopback";
273 version = "8d806ad688961d8840081a609c39d1a82d296b24"; 273 version = "5eaa59e7c41d0e6f35a6c14c3b756d94d25f58ed";
274 src = fetchFromGitHub { 274 src = fetchFromGitHub {
275 owner = "umlaeute"; 275 owner = "umlaeute";
276 repo = "v4l2loopback"; 276 repo = "v4l2loopback";
277 rev = "8d806ad688961d8840081a609c39d1a82d296b24"; 277 rev = "5eaa59e7c41d0e6f35a6c14c3b756d94d25f58ed";
278 fetchSubmodules = true; 278 fetchSubmodules = true;
279 sha256 = "sha256-zuE/qFI8QCWCePmHWjTIPTh2KzmDkwQ2uj5C1dAwo1c="; 279 sha256 = "sha256-YcSpNfItvUdPVirlDyGdYuCnVvxHhh780x+OI5VNZmE=";
280 }; 280 };
281 date = "2025-04-29"; 281 date = "2025-08-18";
282 }; 282 };
283 xcompose = { 283 xcompose = {
284 pname = "xcompose"; 284 pname = "xcompose";
285 version = "8b5a6a0c788fd0a4b921d9d3737174defb863873"; 285 version = "4d8eab4d05a19537ce79294ae0459fdae78ffb20";
286 src = fetchFromGitHub { 286 src = fetchFromGitHub {
287 owner = "kragen"; 287 owner = "kragen";
288 repo = "xcompose"; 288 repo = "xcompose";
289 rev = "8b5a6a0c788fd0a4b921d9d3737174defb863873"; 289 rev = "4d8eab4d05a19537ce79294ae0459fdae78ffb20";
290 fetchSubmodules = false; 290 fetchSubmodules = false;
291 sha256 = "sha256-6EjQErdBOd5hqcrdaf88E1UZVYIc3FOfv34hvUwOWdA="; 291 sha256 = "sha256-vKY4u5Z2IL111orLLkkF4AoVzqluKG/VQhNUUCqO/k8=";
292 }; 292 };
293 date = "2025-03-11"; 293 date = "2025-06-05";
294 }; 294 };
295 yt-dlp = { 295 yt-dlp = {
296 pname = "yt-dlp"; 296 pname = "yt-dlp";
297 version = "2025.4.30"; 297 version = "2025.8.20";
298 src = fetchurl { 298 src = fetchurl {
299 url = "https://pypi.org/packages/source/y/yt_dlp/yt_dlp-2025.4.30.tar.gz"; 299 url = "https://pypi.org/packages/source/y/yt_dlp/yt_dlp-2025.8.20.tar.gz";
300 sha256 = "sha256-0BNn0MOulONcseLsy3p8cOGBxMpEj07iN08mSJ0mNgM="; 300 sha256 = "sha256-2oc7z0JBd6tcO3AfqU6kzawXvzrsXvN7kfUwyQ3ve88=";
301 }; 301 };
302 }; 302 };
303} 303}
diff --git a/accounts/gkleen@sif/default.nix b/accounts/gkleen@sif/default.nix
index 8a848e8f..64434bb8 100644
--- a/accounts/gkleen@sif/default.nix
+++ b/accounts/gkleen@sif/default.nix
@@ -71,7 +71,8 @@ in {
71 imports = [ 71 imports = [
72 ./libvirt 72 ./libvirt
73 ./niri 73 ./niri
74 flakeInputs.nix-index-database.hmModules.nix-index 74 ./synadm
75 flakeInputs.nix-index-database.homeModules.nix-index
75 flakeInputs.impermanence.nixosModules.home-manager.impermanence 76 flakeInputs.impermanence.nixosModules.home-manager.impermanence
76 ]; 77 ];
77 78
@@ -171,6 +172,7 @@ in {
171 }; 172 };
172 }; 173 };
173 }; 174 };
175 chromium.enable = true;
174 176
175 zathura = { 177 zathura = {
176 enable = true; 178 enable = true;
@@ -232,6 +234,7 @@ in {
232 config.programs.ssh.package 234 config.programs.ssh.package
233 gnused 235 gnused
234 miniserve 236 miniserve
237 p7zip
235 ]; 238 ];
236 execer = with pkgs; [ 239 execer = with pkgs; [
237 "cannot:${lib.getExe' rpm "rpm2cpio"}" 240 "cannot:${lib.getExe' rpm "rpm2cpio"}"
@@ -244,6 +247,7 @@ in {
244 "cannot:${lib.getExe less}" 247 "cannot:${lib.getExe less}"
245 "cannot:${lib.getExe' config.systemd.package "systemctl"}" 248 "cannot:${lib.getExe' config.systemd.package "systemctl"}"
246 "cannot:${lib.getExe config.programs.ssh.package}" 249 "cannot:${lib.getExe config.programs.ssh.package}"
250 "cannot:${lib.getExe' p7zip "7z"}"
247 ]; 251 ];
248 wrapper = with pkgs; [ 252 wrapper = with pkgs; [
249 "${lib.getExe' magickWrapped "magick"}:${lib.getExe' imagemagick "magick"}" 253 "${lib.getExe' magickWrapped "magick"}:${lib.getExe' imagemagick "magick"}"
@@ -327,8 +331,10 @@ in {
327 # notify_on_cmd_finish = "invisible 120"; 331 # notify_on_cmd_finish = "invisible 120";
328 }; 332 };
329 keybindings = { 333 keybindings = {
330 "kitty_mod+n" = "detach_window"; 334 "kitty_mod+n" = "new_os_window_with_cwd";
331 "kitty_mod+m" = "detach_window ask"; 335 "kitty_mod+m" = "detach_window ask";
336 "kitty_mod+enter" = "new_window_with_cwd";
337 "kitty_mod+t" = "new_tab_with_cwd";
332 }; 338 };
333 }; 339 };
334 fuzzel = { 340 fuzzel = {
@@ -341,7 +347,7 @@ in {
341 font = "Fira Sans"; 347 font = "Fira Sans";
342 }; 348 };
343 colors = { 349 colors = {
344 background = "000000aa"; 350 background = "000000cc";
345 text = "cdd6f4ff"; 351 text = "cdd6f4ff";
346 match = "94e2d5ff"; 352 match = "94e2d5ff";
347 selection = "585b70ff"; 353 selection = "585b70ff";
@@ -362,6 +368,7 @@ in {
362 enable = true; 368 enable = true;
363 settings.show_banner = false; 369 settings.show_banner = false;
364 }; 370 };
371 fd.enable = true;
365 }; 372 };
366 373
367 services = { 374 services = {
@@ -487,6 +494,13 @@ in {
487 }; 494 };
488 }; 495 };
489 496
497 qt.kde.settings = {
498 kwalletrc = {
499 KSecretD.Enabled = false;
500 Wallet."Default Wallet" = "store";
501 };
502 };
503
490 xsession.preferStatusNotifierItems = true; 504 xsession.preferStatusNotifierItems = true;
491 505
492 xresources.properties = import ./xresources.nix; 506 xresources.properties = import ./xresources.nix;
@@ -498,16 +512,15 @@ in {
498 wrappedYTMDesktop libsForQt5.qt5ct playerctl evince papers 512 wrappedYTMDesktop libsForQt5.qt5ct playerctl evince papers
499 thunderbird zoom-us xdg-desktop-portal steam steam-run 513 thunderbird zoom-us xdg-desktop-portal steam steam-run
500 wireshark virt-manager rclone cached-nix-shell worktime 514 wireshark virt-manager rclone cached-nix-shell worktime
501 fira-code-symbols libreoffice xournalpp google-chrome 515 fira-code-symbols libreoffice xournalpp
502 nixos-shell virt-viewer freerdp gnome-icon-theme 516 nixos-shell virt-viewer freerdp gnome-icon-theme
503 paper-icon-theme sshpassSecret weechat element-desktop 517 paper-icon-theme sshpassSecret weechat element-desktop
504 sieve-connect gimp3 inkscape udiskie glab nitrokey-app 518 sieve-connect gimp3 inkscape udiskie glab nitrokey-app
505 pynitrokey gtklock wlrctl remmina openscad spice-record 519 pynitrokey gtklock wlrctl remmina openscad spice-record
506 libguestfs-with-appliance nerd-fonts.fira-mono 520 nerd-fonts.fira-mono
507 nerd-fonts.symbols-only nerd-fonts.fira-code powerline-fonts 521 nerd-fonts.symbols-only nerd-fonts.fira-code powerline-fonts
508 swtpm (hunspellWithDicts (with hunspellDicts; [en_GB-large de_DE])) 522 swtpm (hunspell.withDicts (dicts: with dicts; [en_GB-large de_DE]))
509 libation 523 libation libqalculate
510 # synadm
511 ] ++ mapAttrsToList (_name: pkg: pkgs.callPackage pkg {}) (customUtils.nixImport { dir = ./utils; }); 524 ] ++ mapAttrsToList (_name: pkg: pkgs.callPackage pkg {}) (customUtils.nixImport { dir = ./utils; });
512 525
513 file = { 526 file = {
@@ -530,6 +543,7 @@ in {
530 STACK_XDG = 1; 543 STACK_XDG = 1;
531 EDITOR = lib.getExe' editor "emacsclient"; 544 EDITOR = lib.getExe' editor "emacsclient";
532 RCLONE_PASSWORD_COMMAND = "${lib.getExe' pkgs.libsecret "secret-tool"} lookup service rclone"; 545 RCLONE_PASSWORD_COMMAND = "${lib.getExe' pkgs.libsecret "secret-tool"} lookup service rclone";
546 SYSTEMD_TINT_BACKGROUND = "false";
533 }; 547 };
534 548
535 extraProfileCommands = '' 549 extraProfileCommands = ''
@@ -566,9 +580,17 @@ in {
566 General = { 580 General = {
567 dot_as_separator = 0; 581 dot_as_separator = 0;
568 }; 582 };
583 Mode = {
584 calculate_as_you_type = 1;
585 };
569 }; 586 };
570 }; 587 };
571 "emacs/init.el".source = ./emacs.el; 588 "emacs/init.el".source = pkgs.substitute {
589 src = ./emacs.el;
590 substitutions = [
591 "--subst-var-by" "ksshaskpass" (lib.getExe pkgs.kdePackages.ksshaskpass)
592 ];
593 };
572 "systemd/user/xdg-desktop-portal.service.d/after-graphical-session.conf".text = '' 594 "systemd/user/xdg-desktop-portal.service.d/after-graphical-session.conf".text = ''
573 [Unit] 595 [Unit]
574 After=graphical-session.target 596 After=graphical-session.target
@@ -586,6 +608,8 @@ in {
586 xdg.dataFile = { 608 xdg.dataFile = {
587 "dbus-1/services/org.keepassxc.KeePassXC.service".source = "${wrappedKeepassxc}/share/dbus-1/services/org.keepassxc.KeePassXC.service"; 609 "dbus-1/services/org.keepassxc.KeePassXC.service".source = "${wrappedKeepassxc}/share/dbus-1/services/org.keepassxc.KeePassXC.service";
588 "dbus-1/services/org.freedesktop.secrets.service.service".source = "${wrappedKeepassxc}/share/dbus-1/services/org.freedesktop.secrets.service.service"; 610 "dbus-1/services/org.freedesktop.secrets.service.service".source = "${wrappedKeepassxc}/share/dbus-1/services/org.freedesktop.secrets.service.service";
611 "dbus-1/services/org.kde.kwalletd6.service".source = "${pkgs.kdePackages.kwallet}/share/dbus-1/services/org.kde.kwalletd6.service";
612 "dbus-1/services/org.kde.kwalletd5.service".source = "${pkgs.kdePackages.kwallet}/share/dbus-1/services/org.kde.kwalletd5.service";
589 "emoji-data/list.txt".source = pkgs.stdenv.mkDerivation { 613 "emoji-data/list.txt".source = pkgs.stdenv.mkDerivation {
590 inherit (sources.emoji-data) pname src; 614 inherit (sources.emoji-data) pname src;
591 version = lib.removePrefix "v" sources.emoji-data.version; 615 version = lib.removePrefix "v" sources.emoji-data.version;
@@ -673,11 +697,11 @@ in {
673 exec -- \ 697 exec -- \
674 ${lib.getExe' config.systemd.package "systemd-run"} --wait --user --slice-inherit \ 698 ${lib.getExe' config.systemd.package "systemd-run"} --wait --user --slice-inherit \
675 --property 'CPUAccounting=yes' --property 'CPUQuotaPeriodSec=50ms' \ 699 --property 'CPUAccounting=yes' --property 'CPUQuotaPeriodSec=50ms' \
676 --property 'Environment=DSCP=46' \ 700 -E DSCP=46 -E NIXOS_OZONE_WL \
677 -- ${lib.getExe pkgs.dscp} ${lib.getExe' pkgs.google-chrome "google-chrome-stable"} \ 701 -- ${lib.getExe pkgs.dscp} ${lib.getExe cfg.programs.chromium.package} \
678 --class=Rainbow \ 702 --class=Rainbow \
679 --kiosk "https://web.openrainbow.com" \ 703 --app="https://web.openrainbow.com" \
680 --user-data-dir=''${HOME}/.config/google-chrome-rainbow 704 --user-data-dir=''${HOME}/.config/chromium-rainbow
681 ''); 705 '');
682 icon = pkgs.fetchurl { 706 icon = pkgs.fetchurl {
683 url = "https://web.openrainbow.com/rb/2.139.17/assets/skins/rainbow/images/homepage/logo__rainbow.svg"; 707 url = "https://web.openrainbow.com/rb/2.139.17/assets/skins/rainbow/images/homepage/logo__rainbow.svg";
@@ -687,6 +711,42 @@ in {
687 StartupWMClass = "Rainbow"; 711 StartupWMClass = "Rainbow";
688 }; 712 };
689 }; 713 };
714 kimai = {
715 name = "Kimai";
716 exec = toString (pkgs.writeShellScript "kimai" ''
717 exec -- \
718 ${lib.getExe cfg.programs.chromium.package} \
719 --class=Kimai \
720 --app="https://kimai.yggdrasil.li" \
721 --user-data-dir=''${HOME}/.config/chromium-kimai
722 '');
723 icon = pkgs.fetchurl {
724 url = "https://www.kimai.org/images/kimai_logo.png";
725 hash = "sha256-lnlOttzR2SwXA70R+egJUkeKr4U5V0avqTk8uX4bqfs=";
726 };
727 settings = {
728 StartupWMClass = "Kimai";
729 StartupNotify = "true";
730 };
731 };
732 audiobookshelf = {
733 name = "Audiobookshelf";
734 exec = toString (pkgs.writeShellScript "audiobookshelf" ''
735 exec -- \
736 ${lib.getExe cfg.programs.chromium.package} \
737 --class=Audiobookshelf \
738 --app="https://audiobookshelf.yggdrasil.li" \
739 --user-data-dir=''${HOME}/.config/chromium-audiobookshelf
740 '');
741 icon = pkgs.fetchurl {
742 url = "https://www.audiobookshelf.org/Logo.png";
743 hash = "sha256-JGPk+WNT1C4DC4lSMb0K0YmAMT5LvmSOeO0QRzkc7Lk=";
744 };
745 settings = {
746 StartupWMClass = "Audiobookshelf";
747 StartupNotify = "true";
748 };
749 };
690 thunderbird-lmu = { 750 thunderbird-lmu = {
691 name = "Thunderbird (LMU)"; 751 name = "Thunderbird (LMU)";
692 exec = "thunderbird --name thunderbird -P lmu %U"; 752 exec = "thunderbird --name thunderbird -P lmu %U";
diff --git a/accounts/gkleen@sif/emacs.el b/accounts/gkleen@sif/emacs.el
index 563c5d0b..3beefba6 100644
--- a/accounts/gkleen@sif/emacs.el
+++ b/accounts/gkleen@sif/emacs.el
@@ -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/niri/default.nix b/accounts/gkleen@sif/niri/default.nix
index fde08ae9..35a3d799 100644
--- a/accounts/gkleen@sif/niri/default.nix
+++ b/accounts/gkleen@sif/niri/default.nix
@@ -3,6 +3,7 @@ let
3 cfg = config.programs.niri; 3 cfg = config.programs.niri;
4 4
5 kdl = flakeInputs.niri-flake.lib.kdl; 5 kdl = flakeInputs.niri-flake.lib.kdl;
6 sleaf = name: arg: kdl.node name [arg] [];
6 7
7 niri = cfg.package; 8 niri = cfg.package;
8 terminal = lib.getExe config.programs.kitty.package; 9 terminal = lib.getExe config.programs.kitty.package;
@@ -35,7 +36,11 @@ let
35 if jq -e '.is_focused' <<<"$window_json" >/dev/null; then 36 if jq -e '.is_focused' <<<"$window_json" >/dev/null; then
36 niri msg action focus-workspace-previous 37 niri msg action focus-workspace-previous
37 else 38 else
38 niri msg action focus-window --id "$(jq -r '.id' <<<"$window_json")" 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
39 fi 44 fi
40 exit 0 45 exit 0
41 fi 46 fi
@@ -130,7 +135,7 @@ let
130 135
131 windows_json="$(niri msg -j windows)" 136 windows_json="$(niri msg -j windows)"
132 active_workspace="$(jq -r '.[] | select(.is_focused) | .workspace_id' <<<"$windows_json")" 137 active_workspace="$(jq -r '.[] | select(.is_focused) | .workspace_id' <<<"$windows_json")"
133 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)"
134 # shellcheck disable=SC2016 139 # shellcheck disable=SC2016
135 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")"
136 141
@@ -143,7 +148,7 @@ let
143 148
144 with_predicate_window = pred: pkgs.writeShellApplication { 149 with_predicate_window = pred: pkgs.writeShellApplication {
145 name = "with-predicate-window"; 150 name = "with-predicate-window";
146 runtimeInputs = [ niri pkgs.gojq pkgs.socat config.programs.fuzzel.package pkgs.gawk ]; 151 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
147 text = '' 152 text = ''
148 action="$1" 153 action="$1"
149 shift 154 shift
@@ -445,8 +450,8 @@ in {
445 { title = "^Access Request.*"; } 450 { title = "^Access Request.*"; }
446 { title = ".*Passkey credentials$"; } 451 { title = ".*Passkey credentials$"; }
447 ]; 452 ];
448 windowRuleExtra = [ 453 windowRuleExtra = with kdl; [
449 (kdl.leaf "open-focused" false) 454 (sleaf "open-focused" false)
450 ]; 455 ];
451 key = "Mod+Control+P"; 456 key = "Mod+Control+P";
452 app-id = "org.keepassxc.KeePassXC"; 457 app-id = "org.keepassxc.KeePassXC";
@@ -473,6 +478,20 @@ in {
473 app-id = "com.github.wwmm.easyeffects"; 478 app-id = "com.github.wwmm.easyeffects";
474 spawn = [ "easyeffects" ]; 479 spawn = [ "easyeffects" ];
475 } 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 }
476 ]; 495 ];
477 programs.niri.config = 496 programs.niri.config =
478 let 497 let
@@ -482,10 +501,12 @@ in {
482 then v 501 then v
483 else null; 502 else null;
484 opt-props = lib.filterAttrs (lib.const (value: value != null)); 503 opt-props = lib.filterAttrs (lib.const (value: value != null));
504 normalize-nodes = nodes: lib.remove null (lib.flatten nodes);
485 in 505 in
486 [ (flag "prefer-no-csd") 506 normalize-nodes [
507 (flag "prefer-no-csd")
487 508
488 (leaf "screenshot-path" "~/screenshots/%Y-%m-%dT%H:%M:%S.png") 509 (sleaf "screenshot-path" "~/screenshots/%Y-%m-%dT%H:%M:%S.png")
489 510
490 (plain "hotkey-overlay" [ 511 (plain "hotkey-overlay" [
491 (flag "skip-at-startup") 512 (flag "skip-at-startup")
@@ -493,27 +514,27 @@ in {
493 514
494 (plain "input" [ 515 (plain "input" [
495 (plain "keyboard" [ 516 (plain "keyboard" [
496 (leaf "repeat-delay" 300) 517 (sleaf "repeat-delay" 300)
497 (leaf "repeat-rate" 50) 518 (sleaf "repeat-rate" 50)
498 519
499 (plain "xkb" [ 520 (plain "xkb" [
500 (leaf "layout" "us,us") 521 (sleaf "layout" "us,us")
501 (leaf "variant" "dvp,") 522 (sleaf "variant" "dvp,")
502 (leaf "options" "compose:caps,grp:win_space_toggle") 523 (sleaf "options" "compose:caps,grp:win_space_toggle")
503 ]) 524 ])
504 ]) 525 ])
505 526
506 (flag "workspace-auto-back-and-forth") 527 (flag "workspace-auto-back-and-forth")
507 # (leaf "focus-follows-mouse" {}) 528 # (sleaf "focus-follows-mouse" {})
508 # (flag "warp-mouse-to-focus") 529 # (flag "warp-mouse-to-focus")
509 530
510 # (plain "touchpad" [ (flag "off") ]) 531 # (plain "touchpad" [ (flag "off") ])
511 (plain "trackball" [ 532 (plain "trackball" [
512 (leaf "scroll-method" "on-button-down") 533 (sleaf "scroll-method" "on-button-down")
513 (leaf "scroll-button" 278) 534 (sleaf "scroll-button" 278)
514 ]) 535 ])
515 (plain "touch" [ 536 (plain "touch" [
516 (leaf "map-to-output" "eDP-1") 537 (sleaf "map-to-output" "eDP-1")
517 ]) 538 ])
518 ]) 539 ])
519 540
@@ -521,7 +542,7 @@ in {
521 (plain "hot-corners" [(flag "off")]) 542 (plain "hot-corners" [(flag "off")])
522 ]) 543 ])
523 544
524 (plain "environment" (lib.mapAttrsToList leaf { 545 (plain "environment" (lib.mapAttrsToList sleaf {
525 NIXOS_OZONE_WL = "1"; 546 NIXOS_OZONE_WL = "1";
526 QT_QPA_PLATFORM = "wayland"; 547 QT_QPA_PLATFORM = "wayland";
527 QT_WAYLAND_DISABLE_WINDOWDECORATION = "1"; 548 QT_WAYLAND_DISABLE_WINDOWDECORATION = "1";
@@ -529,49 +550,52 @@ in {
529 SDL_VIDEODRIVER = "wayland"; 550 SDL_VIDEODRIVER = "wayland";
530 DISPLAY = ":0"; 551 DISPLAY = ":0";
531 ELECTRON_OZONE_PLATFORM_HINT = "auto"; 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;
532 })) 556 }))
533 557
534 (node "output" "eDP-1" [ 558 (node "output" ["eDP-1"] [
535 (leaf "scale" 1.5) 559 (sleaf "scale" 1.5)
536 (leaf "position" { x = 0; y = 0; }) 560 (sleaf "position" { x = 0; y = 0; })
537 ]) 561 ])
538 (node "output" "Ancor Communications Inc ASUS PB287Q 0x0000DD9B" [ 562 (node "output" ["Ancor Communications Inc ASUS PB287Q 0x0000DD9B"] [
539 (leaf "scale" 1.5) 563 (sleaf "scale" 1.5)
540 (leaf "position" { x = 2560; y = 0; }) 564 (sleaf "position" { x = 2560; y = 0; })
541 ]) 565 ])
542 (node "output" "HP Inc. HP 727pu CN4417143K" [ 566 (node "output" ["HP Inc. HP 727pu CN4417143K"] [
543 (leaf "mode" "2560x1440@119.998") 567 (sleaf "mode" "2560x1440@119.998")
544 (leaf "scale" 1) 568 (sleaf "scale" 1)
545 (leaf "position" { x = 2560; y = 0; }) 569 (sleaf "position" { x = 2560; y = 0; })
546 (flag "variable-refresh-rate") 570 (flag "variable-refresh-rate")
547 ]) 571 ])
548 572
549 (plain "debug" [ 573 (plain "debug" [
550 (leaf "render-drm-device" "/dev/dri/by-path/pci-0000:00:02.0-render") 574 (sleaf "render-drm-device" "/dev/dri/by-path/pci-0000:00:02.0-render")
551 ]) 575 ])
552 576
553 (plain "animations" [ 577 (plain "animations" [
554 (leaf "slowdown" 0.5) 578 (sleaf "slowdown" 0.5)
555 (plain "workspace-switch" [(flag "off")]) 579 (plain "workspace-switch" [(flag "off")])
556 ]) 580 ])
557 581
558 (plain "layout" [ 582 (plain "layout" [
559 (leaf "gaps" 8) 583 (sleaf "gaps" 8)
560 (plain "struts" [ 584 (plain "struts" [
561 (leaf "left" 26) 585 (sleaf "left" 26)
562 (leaf "right" 26) 586 (sleaf "right" 26)
563 (leaf "top" 0) 587 (sleaf "top" 0)
564 (leaf "bottom" 0) 588 (sleaf "bottom" 0)
565 ]) 589 ])
566 (plain "border" [ 590 (plain "border" [
567 (leaf "width" 2) 591 (sleaf "width" 2)
568 (leaf "active-gradient" { 592 (sleaf "active-gradient" {
569 from = "hsla(195 100% 45% 1)"; 593 from = "hsla(195 100% 45% 1)";
570 to = "hsla(155 100% 37.5% 1)"; 594 to = "hsla(155 100% 37.5% 1)";
571 angle = 29; 595 angle = 29;
572 relative-to = "workspace-view"; 596 relative-to = "workspace-view";
573 }) 597 })
574 (leaf "inactive-gradient" { 598 (sleaf "inactive-gradient" {
575 from = "hsla(0 0% 27.7% 1)"; 599 from = "hsla(0 0% 27.7% 1)";
576 to = "hsla(0 0% 23% 1)"; 600 to = "hsla(0 0% 23% 1)";
577 angle = 29; 601 angle = 29;
@@ -582,29 +606,29 @@ in {
582 (flag "off") 606 (flag "off")
583 ]) 607 ])
584 608
585 (plain "preset-column-widths" (map (prop: leaf "proportion" prop) [ 609 (plain "preset-column-widths" (map (prop: sleaf "proportion" prop) [
586 (1. / 4.) (1. / 3.) (1. / 2.) (2. / 3.) (3. / 4.) (1.) 610 (1. / 4.) (1. / 3.) (1. / 2.) (2. / 3.) (3. / 4.) (1.)
587 ])) 611 ]))
588 (plain "default-column-width" [ (leaf "proportion" (1. / 2.)) ]) 612 (plain "default-column-width" [ (sleaf "proportion" (1. / 2.)) ])
589 (plain "preset-window-heights" (map (prop: leaf "proportion" prop) [ 613 (plain "preset-window-heights" (map (prop: sleaf "proportion" prop) [
590 (1. / 3.) (1. / 2.) (2. / 3.) (1.) 614 (1. / 3.) (1. / 2.) (2. / 3.) (1.)
591 ])) 615 ]))
592 616
593 (flag "always-center-single-column") 617 (flag "always-center-single-column")
594 618
595 (plain "tab-indicator" [ 619 (plain "tab-indicator" [
596 (leaf "gap" 4) 620 (sleaf "gap" 4)
597 (leaf "width" 8) 621 (sleaf "width" 8)
598 (leaf "gaps-between-tabs" 4) 622 (sleaf "gaps-between-tabs" 4)
599 (flag "place-within-column") 623 (flag "place-within-column")
600 (leaf "length" { total-proportion = 1.; }) 624 (sleaf "length" { total-proportion = 1.; })
601 (leaf "active-gradient" { 625 (sleaf "active-gradient" {
602 from = "hsla(195 100% 60% 0.75)"; 626 from = "hsla(195 100% 60% 0.75)";
603 to = "hsla(155 100% 50% 0.75)"; 627 to = "hsla(155 100% 50% 0.75)";
604 angle = 29; 628 angle = 29;
605 relative-to = "workspace-view"; 629 relative-to = "workspace-view";
606 }) 630 })
607 (leaf "inactive-gradient" { 631 (sleaf "inactive-gradient" {
608 from = "hsla(0 0% 42% 0.66)"; 632 from = "hsla(0 0% 42% 0.66)";
609 to = "hsla(0 0% 35% 0.66)"; 633 to = "hsla(0 0% 35% 0.66)";
610 angle = 29; 634 angle = 29;
@@ -618,124 +642,121 @@ in {
618 ]) 642 ])
619 643
620 (map (name: 644 (map (name:
621 (node "workspace" name [ 645 (node "workspace" [name] [
622 (leaf "open-on-output" "eDP-1") 646 (sleaf "open-on-output" "eDP-1")
623 ]) 647 ])
624 ) (map ({name, ...}: name) cfg.scratchspaces)) 648 ) (map ({name, ...}: name) cfg.scratchspaces))
625 (map (name: 649 (map (name:
626 (leaf "workspace" name) 650 (sleaf "workspace" name)
627 ) ["comm" "web" "vid" "bmr"]) 651 ) ["comm" "web" "vid" "bmr"])
628 652
629 (plain "window-rule" [ 653 (plain "window-rule" [
630 (leaf "clip-to-geometry" true) 654 (sleaf "clip-to-geometry" true)
631 ]) 655 ])
632 656
633 (plain "window-rule" [ 657 (plain "window-rule" [
634 (leaf "match" { is-floating = true; }) 658 (sleaf "match" { is-floating = true; })
635 (leaf "geometry-corner-radius" 8) 659 (sleaf "geometry-corner-radius" 8)
636 (plain "shadow" [ (flag "on") ]) 660 (plain "shadow" [ (flag "on") ])
637 ]) 661 ])
638 662
639 (plain "window-rule" [ 663 (plain "window-rule" [
640 (leaf "match" { app-id = "^org\\.keepassxc\\.KeePassXC$"; }) 664 (sleaf "match" { app-id = "^org\\.keepassxc\\.KeePassXC$"; })
641 (leaf "block-out-from" "screencast") 665 (sleaf "block-out-from" "screencast")
642 ]) 666 ])
643 (plain "window-rule" [ 667 (plain "window-rule" (normalize-nodes [
644 (map (title: 668 (map (title:
645 (leaf "match" { app-id = "^org\\.keepassxc\\.KeePassXC$"; inherit title; }) 669 (sleaf "match" { app-id = "^org\\.keepassxc\\.KeePassXC$"; inherit title; })
646 ) ["^Unlock Database.*" "^Access Request.*" ".*Passkey credentials$"]) 670 ) ["^Unlock Database.*" "^Access Request.*" ".*Passkey credentials$" "Browser Access Request$"])
647 (leaf "open-focused" true) 671 (sleaf "open-focused" true)
648 (leaf "open-floating" true) 672 (sleaf "open-floating" true)
649 ]) 673 ]))
650 674
651 (map ({ name, match, exclude, windowRuleExtra, ... }: 675 (map ({ name, match, exclude, windowRuleExtra, ... }:
652 (optional-node (match != []) (plain "window-rule" [ 676 (optional-node (match != []) (plain "window-rule" (normalize-nodes [
653 (map (leaf "match") match) 677 (map (sleaf "match") match)
654 (map (leaf "exclude") exclude) 678 (map (sleaf "exclude") exclude)
655 (leaf "open-on-workspace" name) 679 (sleaf "open-on-workspace" name)
656 (leaf "open-maximized" true) 680 (sleaf "open-maximized" true)
657 windowRuleExtra 681 windowRuleExtra
658 ])) 682 ])))
659 ) cfg.scratchspaces) 683 ) cfg.scratchspaces)
660 684
661 (plain "window-rule" [ 685 (plain "window-rule" [
662 (leaf "match" { app-id = "^emacs$"; }) 686 (sleaf "match" { app-id = "^emacs$"; })
663 (leaf "match" { app-id = "^firefox$"; }) 687 (sleaf "match" { app-id = "^firefox$"; })
664 (plain "default-column-width" [(leaf "proportion" (2. / 3.))]) 688 (plain "default-column-width" [(sleaf "proportion" (2. / 3.))])
665 ]) 689 ])
666 (plain "window-rule" [ 690 (plain "window-rule" [
667 (leaf "match" { app-id = "^kitty$"; }) 691 (sleaf "match" { app-id = "^kitty$"; })
668 (leaf "match" { app-id = "^kitty-play$"; }) 692 (sleaf "match" { app-id = "^kitty-play$"; })
669 (plain "default-column-width" [(leaf "proportion" (1. / 3.))]) 693 (plain "default-column-width" [(sleaf "proportion" (1. / 3.))])
670 ]) 694 ])
671 695
672 (plain "window-rule" [ 696 (plain "window-rule" [
673 (leaf "match" { app-id = "^thunderbird$"; }) 697 (sleaf "match" { app-id = "^thunderbird$"; })
674 (leaf "match" { app-id = "^Element$"; }) 698 (sleaf "match" { app-id = "^Element$"; })
675 (leaf "match" { app-id = "^Rainbow$"; }) 699 (sleaf "match" { app-id = "^chrome-web\.openrainbow\.com__-Default$"; })
676 (leaf "open-on-workspace" "comm") 700 (sleaf "open-on-workspace" "comm")
677 ]) 701 ])
678 (plain "window-rule" [ 702 (plain "window-rule" [
679 (leaf "match" { app-id = "^firefox$"; }) 703 (sleaf "match" { app-id = "^firefox$"; })
680 (leaf "open-on-workspace" "web") 704 (sleaf "open-on-workspace" "web")
681 (leaf "open-maximized" true) 705 (sleaf "open-maximized" true)
682 ]) 706 ])
683 (plain "window-rule" [ 707 (plain "window-rule" [
684 (leaf "match" { app-id = "^mpv$"; }) 708 (sleaf "match" { app-id = "^mpv$"; })
685 (leaf "open-on-workspace" "vid") 709 (sleaf "open-on-workspace" "vid")
686 (plain "default-column-width" [(leaf "proportion" 1.)]) 710 (plain "default-column-width" [(sleaf "proportion" 1.)])
687 ]) 711 ])
688 (plain "window-rule" [ 712 (plain "window-rule" [
689 (leaf "match" { app-id = "^kitty-play$"; }) 713 (sleaf "match" { app-id = "^kitty-play$"; })
690 (leaf "open-on-workspace" "vid") 714 (sleaf "open-on-workspace" "vid")
691 (leaf "open-focused" false) 715 (sleaf "open-focused" false)
692 ]) 716 ])
693 (plain "window-rule" [ 717 (plain "window-rule" [
694 (leaf "match" { app-id = "^pdfpc$"; }) 718 (sleaf "match" { app-id = "^chrome-audiobookshelf\.yggdrasil\.li__-Default$"; })
695 (plain "default-column-width" [(leaf "proportion" 1.)]) 719 (sleaf "match" { app-id = "^YouTube Music Desktop App$"; })
720 (sleaf "open-on-workspace" "vid")
696 ]) 721 ])
697 (plain "window-rule" [ 722 (plain "window-rule" [
698 (leaf "match" { app-id = "^pdfpc$"; title = "^.*presentation.*$"; }) 723 (sleaf "match" { app-id = "^pdfpc$"; })
699 (plain "default-column-width" [(leaf "proportion" 1.)]) 724 (plain "default-column-width" [(sleaf "proportion" 1.)])
700 (leaf "open-fullscreen" true)
701 (leaf "open-on-workspace" "bmr")
702 (leaf "open-focused" false)
703 ]) 725 ])
704 (plain "window-rule" [ 726 (plain "window-rule" [
705 (map (leaf "match") [ 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") [
706 { app-id = "^Gimp-"; title = "^Quit GIMP$"; } 735 { app-id = "^Gimp-"; title = "^Quit GIMP$"; }
707 { app-id = "^org\\.kde\\.polkit-kde-authentication-agent-1$"; } 736 { app-id = "^org\\.kde\\.polkit-kde-authentication-agent-1$"; }
708 { app-id = "^xdg-desktop-portal-gtk$"; } 737 { app-id = "^xdg-desktop-portal-gtk$"; }
709 ]) 738 ])
710 (leaf "open-floating" true) 739 (sleaf "open-floating" true)
711 ]) 740 ]))
712 (plain "window-rule" [ 741 (plain "window-rule" [
713 (leaf "match" { app-id = "^org\\.pwmt\\.zathura$"; }) 742 (sleaf "match" { app-id = "^org\\.pwmt\\.zathura$"; })
714 (leaf "match" { app-id = "^evince$"; }) 743 (sleaf "match" { app-id = "^evince$"; })
715 (leaf "match" { app-id = "^org\\.gnome\\.Papers$"; }) 744 (sleaf "match" { app-id = "^org\\.gnome\\.Papers$"; })
716 (leaf "default-column-display" "tabbed") 745 (sleaf "default-column-display" "tabbed")
717 ]) 746 ])
718 747
719 (plain "layer-rule" [ 748 (plain "layer-rule" [
720 (leaf "match" { namespace = "^notifications$"; }) 749 (sleaf "match" { namespace = "^notifications$"; })
721 (leaf "match" { namespace = "^waybar$"; }) 750 (sleaf "match" { namespace = "^waybar$"; })
722 (leaf "match" { namespace = "^launcher$"; }) 751 (sleaf "match" { namespace = "^launcher$"; })
723 (leaf "block-out-from" "screencast") 752 (sleaf "block-out-from" "screencast")
724 ]) 753 ])
725 754
726 (plain "binds" 755 (plain "binds"
727 (let 756 (let
728 bind = name: cfg: node name (opt-props { 757 bind = name: cfg: node name [(lib.removeAttrs cfg ["action"])] (lib.mapAttrsToList leaf (lib.removeAttrs cfg.action ["__functor"]));
729 cooldown-ms = cfg.cooldown-ms or null;
730 }
731 // (lib.optionalAttrs (!(cfg.repeat or true)) {
732 repeat = false;
733 })
734 // (lib.optionalAttrs (cfg.allow-when-locked or false) {
735 allow-when-locked = true;
736 })) (lib.mapAttrsToList leaf (lib.removeAttrs cfg.action ["__functor"]));
737 in 758 in
738 [ 759 normalize-nodes [
739 (lib.mapAttrsToList bind (with config.lib.niri.actions; { 760 (lib.mapAttrsToList bind (with config.lib.niri.actions; {
740 "Mod+Slash".action = show-hotkey-overlay; 761 "Mod+Slash".action = show-hotkey-overlay;
741 762
@@ -790,12 +811,12 @@ in {
790 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) 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)
791 $FOUND || echo 812 $FOUND || echo
792 } 813 }
793 FUZZEL_RES=$(prev | fuzzel --dmenu --prompt "qalc> ") || exit $? 814 FUZZEL_RES=$(prev | fuzzel --dmenu --prompt "qalc> " --width=60) || exit $?
794 if [[ "$FUZZEL_RES" =~ .*\ =\ .* ]]; then 815 if [[ "$FUZZEL_RES" =~ .*\ =\ .* ]]; then
795 QALC_RES="$FUZZEL_RES" 816 QALC_RES="$FUZZEL_RES"
796 QALC_RET=0 817 QALC_RET=0
797 else 818 else
798 QALC_RES=$(qalc "$FUZZEL_RES" 2>&1) 819 QALC_RES=$(qalc -set "autocalc off" "$FUZZEL_RES" 2>&1)
799 QALC_RET=$? 820 QALC_RET=$?
800 fi 821 fi
801 [[ -n "$QALC_RES" ]] || exit 1 822 [[ -n "$QALC_RES" ]] || exit 1
@@ -815,11 +836,26 @@ in {
815 notify-send "$QALC_RES" 836 notify-send "$QALC_RES"
816 ''; 837 '';
817 })); 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");
818 "Mod+E".action = spawn (lib.getExe (pkgs.writeShellApplication { 854 "Mod+E".action = spawn (lib.getExe (pkgs.writeShellApplication {
819 name = "emoji-fuzzel"; 855 name = "emoji-fuzzel";
820 runtimeInputs = with pkgs; [ config.programs.fuzzel.package wtype wl-clipboard-rs ]; 856 runtimeInputs = with pkgs; [ config.programs.fuzzel.package wtype wl-clipboard-rs ];
821 text = '' 857 text = ''
822 FUZZEL_RES=$(fuzzel --dmenu --prompt "emoji> " <"$HOME"/.local/share/emoji-data/list.txt) || exit $? 858 FUZZEL_RES=$(fuzzel --dmenu --prompt "emoji> " --cache "$HOME"/.cache/fuzzel-emoji --width=60 <"$HOME"/.local/share/emoji-data/list.txt) || exit $?
823 [[ -n "$FUZZEL_RES" ]] || exit 1 859 [[ -n "$FUZZEL_RES" ]] || exit 1
824 wl-copy "$(cut -d ':' -f 1 <<<"$FUZZEL_RES" | tr -d '\n')" && wtype -k XF86Paste 860 wl-copy "$(cut -d ':' -f 1 <<<"$FUZZEL_RES" | tr -d '\n')" && wtype -k XF86Paste
825 ''; 861 '';
@@ -960,6 +996,9 @@ in {
960 996
961 "Mod+D".action = with-urgent-window-action "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}"; 997 "Mod+D".action = with-urgent-window-action "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
962 "Mod+Shift+D".action = with-focused-window-action "{\"Action\":{\"UnsetUrgent\":{\"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");
963 })) 1002 }))
964 (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) 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)
965 (map ({ name, moveKey, ...}: if moveKey != null then bind moveKey { action = kdl.magic-leaf "move-column-to-workspace" name; } 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)
diff --git a/accounts/gkleen@sif/niri/mako.nix b/accounts/gkleen@sif/niri/mako.nix
index 810bff89..703d5f7b 100644
--- a/accounts/gkleen@sif/niri/mako.nix
+++ b/accounts/gkleen@sif/niri/mako.nix
@@ -14,8 +14,7 @@
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 criteria = {
19 grouped.format = "<b>(%g)</b> <i>%s</i>\\n%b"; 18 grouped.format = "<b>(%g)</b> <i>%s</i>\\n%b";
20 "urgency=low".text-color = "#999999ff"; 19 "urgency=low".text-color = "#999999ff";
21 "urgency=critical".background-color = "#900000dd"; 20 "urgency=critical".background-color = "#900000dd";
@@ -25,6 +24,7 @@
25 ignore-timeout = true; 24 ignore-timeout = true;
26 default-timeout = 2000; 25 default-timeout = 2000;
27 }; 26 };
27 "app-name=worktime".history = false;
28 "mode=silent".invisible = true; 28 "mode=silent".invisible = true;
29 }; 29 };
30 package = pkgs.symlinkJoin { 30 package = pkgs.symlinkJoin {
diff --git a/accounts/gkleen@sif/niri/waybar.nix b/accounts/gkleen@sif/niri/waybar.nix
index cc131c08..c02a9a76 100644
--- a/accounts/gkleen@sif/niri/waybar.nix
+++ b/accounts/gkleen@sif/niri/waybar.nix
@@ -27,8 +27,14 @@ in {
27 modules-right = [ "custom/worktime" "custom/worktime-today" 27 modules-right = [ "custom/worktime" "custom/worktime-today"
28 "custom/weather" 28 "custom/weather"
29 "custom/keymap" 29 "custom/keymap"
30 "privacy" "tray" "wireplumber" "backlight" "battery" "idle_inhibitor" "custom/mako" "clock" ]; 30 "privacy" "tray" "wireplumber" "backlight" "battery" "idle_inhibitor" "custom/mako" "custom/lid_inhibitor" "clock" ];
31 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 };
32 "custom/mako" = { 38 "custom/mako" = {
33 format = "{}"; 39 format = "{}";
34 return-type = "json"; 40 return-type = "json";
@@ -211,7 +217,7 @@ in {
211 layer = "top"; 217 layer = "top";
212 position = "top"; 218 position = "top";
213 height = 14; 219 height = 14;
214 output = [ "!eDP-1" "!DP-2" "!DP-3" ]; 220 output = [ "!eDP-1" "!DP-2" "!DP-3" "*" ];
215 modules-left = [ "niri/workspaces" ]; 221 modules-left = [ "niri/workspaces" ];
216 modules-center = [ "niri/window" ]; 222 modules-center = [ "niri/window" ];
217 modules-right = [ "clock" ]; 223 modules-right = [ "clock" ];
@@ -293,7 +299,7 @@ in {
293 #tray { 299 #tray {
294 margin: 0; 300 margin: 0;
295 } 301 }
296 #battery, #idle_inhibitor, #backlight, #wireplumber, #custom-mako { 302 #battery, #idle_inhibitor, #backlight, #wireplumber, #custom-mako, #custom-lid_inhibitor {
297 color: @grey; 303 color: @grey;
298 margin: 0 5px 0 2px; 304 margin: 0 5px 0 2px;
299 } 305 }
@@ -302,7 +308,11 @@ in {
302 margin-left: 6px; 308 margin-left: 6px;
303 } 309 }
304 #custom-mako { 310 #custom-mako {
305 margin-right: 2px; 311 margin-right: 4px;
312 margin-left: 3px;
313 }
314 #custom-lid_inhibitor {
315 margin-right: 3px;
306 margin-left: 3px; 316 margin-left: 3px;
307 } 317 }
308 #battery { 318 #battery {
@@ -330,7 +340,7 @@ in {
330 color: @orange; 340 color: @orange;
331 } 341 }
332 342
333 #idle_inhibitor { 343 #idle_inhibitor, #custom-lid_inhibitor {
334 padding-top: 1px; 344 padding-top: 1px;
335 } 345 }
336 346
@@ -340,6 +350,7 @@ in {
340 } 350 }
341 #clock { 351 #clock {
342 /* margin-right: 5px; */ 352 /* margin-right: 5px; */
353 font-feature-settings: "tnum";
343 } 354 }
344 ''; 355 '';
345 }; 356 };
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 90cccc58..18c2315f 100644
--- a/accounts/gkleen@sif/systemd.nix
+++ b/accounts/gkleen@sif/systemd.nix
@@ -385,6 +385,8 @@ in {
385 }; 385 };
386 Service = { 386 Service = {
387 ExecStart = "${config.systemd.package}/lib/systemd/systemd-socket-proxyd --exit-idle-time=60s 127.0.0.1:${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";
388 }; 390 };
389 }) [{ host = "proxy.ssh.math.lmu.de"; port = 8118; } { host = "proxy.vidhar"; port = 8120; } { host = "proxy.mathw0h"; port = 8122; } { host = "proxy.mathw0e"; port = 8124; }]); 391 }) [{ host = "proxy.ssh.math.lmu.de"; port = 8118; } { host = "proxy.vidhar"; port = 8120; } { host = "proxy.mathw0h"; port = 8122; } { host = "proxy.mathw0e"; port = 8124; }]);
390 sockets = listToAttrs (map (port: nameValuePair "proxy-to-autossh-socks@${toString port}" { 392 sockets = listToAttrs (map (port: nameValuePair "proxy-to-autossh-socks@${toString port}" {
diff --git a/accounts/gkleen@sif/utils/pdf2pdf.nix b/accounts/gkleen@sif/utils/pdf2pdf.nix
new file mode 100644
index 00000000..5e9fb215
--- /dev/null
+++ b/accounts/gkleen@sif/utils/pdf2pdf.nix
@@ -0,0 +1,8 @@
1pkgs@{ lib, resholve, zsh, ghostscript_headless, ... }:
2
3resholve.writeScriptBin "pdf2pdf" {
4 inputs = with pkgs; [ghostscript_headless];
5 interpreter = lib.getExe zsh;
6} ''
7 exec gs -dPDFSETTINGS=/prepress -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dSAFER "-sOutputFile=''${2}" "''${1}"
8''
diff --git a/accounts/gkleen@sif/zshrc b/accounts/gkleen@sif/zshrc
index abc200c6..702990c3 100644
--- a/accounts/gkleen@sif/zshrc
+++ b/accounts/gkleen@sif/zshrc
@@ -93,7 +93,7 @@ dir() {
93 if [[ $curlArchive = "true" ]]; then 93 if [[ $curlArchive = "true" ]]; then
94 archiveFile=$(mktemp -t "archive.XXXXXXXXXX.${templateArchive:t}") 94 archiveFile=$(mktemp -t "archive.XXXXXXXXXX.${templateArchive:t}")
95 95
96 curl -L -o ${archiveFile} ${templateArchive} 96 curl -SfL -o ${archiveFile} ${templateArchive}
97 97
98 templateArchive=${archiveFile} 98 templateArchive=${archiveFile}
99 fi 99 fi
@@ -132,6 +132,10 @@ dir() {
132 unpack=false 132 unpack=false
133 fi 133 fi
134 ;; 134 ;;
135 application/x-iso9660-image)
136 7z x ${templateArchive}
137 unpack=false
138 ;;
135 *) 139 *)
136 tar -xvaf ${templateArchive} 140 tar -xvaf ${templateArchive}
137 unpack=false 141 unpack=false
@@ -231,7 +235,7 @@ clock() {
231} 235}
232 236
233public-ip() { 237public-ip() {
234 curl -s -H 'Accept: application/json' $@ ifconfig.co | jq -r '.ip' 238 curl -sSf -H 'Accept: application/json' $@ ifconfig.co | jq -r '.ip'
235} 239}
236 240
237swap() { 241swap() {
diff --git a/flake.lock b/flake.lock
index a884d6c6..14568259 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,26 +39,45 @@
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 }
55 }, 65 },
66 "crane": {
67 "locked": {
68 "lastModified": 1731098351,
69 "narHash": "sha256-HQkYvKvaLQqNa10KEFGgWHfMAbWBfFp+4cAgkut+NNE=",
70 "owner": "ipetkov",
71 "repo": "crane",
72 "rev": "ef80ead953c1b28316cc3f8613904edc2eb90c28",
73 "type": "github"
74 },
75 "original": {
76 "owner": "ipetkov",
77 "repo": "crane",
78 "type": "github"
79 }
80 },
56 "deploy-rs": { 81 "deploy-rs": {
57 "inputs": { 82 "inputs": {
58 "flake-compat": [ 83 "flake-compat": [
@@ -66,11 +91,11 @@
66 ] 91 ]
67 }, 92 },
68 "locked": { 93 "locked": {
69 "lastModified": 1727447169, 94 "lastModified": 1749105467,
70 "narHash": "sha256-3KyjMPUKHkiWhwR91J1YchF6zb6gvckCAY1jOE+ne0U=", 95 "narHash": "sha256-hXh76y/wDl15almBcqvjryB50B0BaiXJKk20f314RoE=",
71 "owner": "serokell", 96 "owner": "serokell",
72 "repo": "deploy-rs", 97 "repo": "deploy-rs",
73 "rev": "aa07eb05537d4cd025e2310397a6adcedfe72c76", 98 "rev": "6bc76b872374845ba9d645a2f012b764fecd765f",
74 "type": "github" 99 "type": "github"
75 }, 100 },
76 "original": { 101 "original": {
@@ -132,6 +157,22 @@
132 "flake-compat_4": { 157 "flake-compat_4": {
133 "flake": false, 158 "flake": false,
134 "locked": { 159 "locked": {
160 "lastModified": 1696426674,
161 "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
162 "owner": "edolstra",
163 "repo": "flake-compat",
164 "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
165 "type": "github"
166 },
167 "original": {
168 "owner": "edolstra",
169 "repo": "flake-compat",
170 "type": "github"
171 }
172 },
173 "flake-compat_5": {
174 "flake": false,
175 "locked": {
135 "lastModified": 1673956053, 176 "lastModified": 1673956053,
136 "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", 177 "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
137 "owner": "edolstra", 178 "owner": "edolstra",
@@ -168,11 +209,11 @@
168 "nixpkgs-lib": "nixpkgs-lib_2" 209 "nixpkgs-lib": "nixpkgs-lib_2"
169 }, 210 },
170 "locked": { 211 "locked": {
171 "lastModified": 1733312601, 212 "lastModified": 1749398372,
172 "narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=", 213 "narHash": "sha256-tYBdgS56eXYaWVW3fsnPQ/nFlgWi/Z2Ymhyu21zVM98=",
173 "owner": "hercules-ci", 214 "owner": "hercules-ci",
174 "repo": "flake-parts", 215 "repo": "flake-parts",
175 "rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9", 216 "rev": "9305fe4e5c2a6fcf5ba6a3ff155720fbe4076569",
176 "type": "github" 217 "type": "github"
177 }, 218 },
178 "original": { 219 "original": {
@@ -183,6 +224,27 @@
183 }, 224 },
184 "flake-parts_3": { 225 "flake-parts_3": {
185 "inputs": { 226 "inputs": {
227 "nixpkgs-lib": [
228 "lanzaboote",
229 "nixpkgs"
230 ]
231 },
232 "locked": {
233 "lastModified": 1730504689,
234 "narHash": "sha256-hgmguH29K2fvs9szpq2r3pz2/8cJd2LPS+b4tfNFCwE=",
235 "owner": "hercules-ci",
236 "repo": "flake-parts",
237 "rev": "506278e768c2a08bec68eb62932193e341f55c90",
238 "type": "github"
239 },
240 "original": {
241 "owner": "hercules-ci",
242 "repo": "flake-parts",
243 "type": "github"
244 }
245 },
246 "flake-parts_4": {
247 "inputs": {
186 "nixpkgs-lib": "nixpkgs-lib_3" 248 "nixpkgs-lib": "nixpkgs-lib_3"
187 }, 249 },
188 "locked": { 250 "locked": {
@@ -296,6 +358,28 @@
296 "gitignore_3": { 358 "gitignore_3": {
297 "inputs": { 359 "inputs": {
298 "nixpkgs": [ 360 "nixpkgs": [
361 "lanzaboote",
362 "pre-commit-hooks-nix",
363 "nixpkgs"
364 ]
365 },
366 "locked": {
367 "lastModified": 1709087332,
368 "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
369 "owner": "hercules-ci",
370 "repo": "gitignore.nix",
371 "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
372 "type": "github"
373 },
374 "original": {
375 "owner": "hercules-ci",
376 "repo": "gitignore.nix",
377 "type": "github"
378 }
379 },
380 "gitignore_4": {
381 "inputs": {
382 "nixpkgs": [
299 "prometheus-borg-exporter", 383 "prometheus-borg-exporter",
300 "pre-commit-hooks-nix", 384 "pre-commit-hooks-nix",
301 "nixpkgs" 385 "nixpkgs"
@@ -322,11 +406,11 @@
322 ] 406 ]
323 }, 407 },
324 "locked": { 408 "locked": {
325 "lastModified": 1746904907, 409 "lastModified": 1753177987,
326 "narHash": "sha256-XYo6bwc7xwo4lO6a/D2ttYRN4yDmsAjyt5O1E0vOLDg=", 410 "narHash": "sha256-PkCc+YTrl0A/H6EV09DCr5yZpvQZ9DkuFXj/NNaEvHs=",
327 "owner": "gkleen", 411 "owner": "gkleen",
328 "repo": "home-manager", 412 "repo": "home-manager",
329 "rev": "696495266c65b76f08d8196b87aa7bd835906570", 413 "rev": "b493410fc6e427129a1caee8f50970d152a27daa",
330 "type": "github" 414 "type": "github"
331 }, 415 },
332 "original": { 416 "original": {
@@ -343,16 +427,16 @@
343 ] 427 ]
344 }, 428 },
345 "locked": { 429 "locked": {
346 "lastModified": 1747139300, 430 "lastModified": 1749562430,
347 "narHash": "sha256-V+YnIIM2wMprHGgzOU0HzyeWQEjP6EhG8kc4IffWFeg=", 431 "narHash": "sha256-M5MqsIsf+o7yngakVUW4poBGZaghB6sUpw7SsWA55kU=",
348 "owner": "gkleen", 432 "owner": "gkleen",
349 "repo": "home-manager", 433 "repo": "home-manager",
350 "rev": "50182497604587a24bdbe97d6400b1696eac57b1", 434 "rev": "dca5a2df9f8a00cc34bea6ead249a8446f5f069e",
351 "type": "github" 435 "type": "github"
352 }, 436 },
353 "original": { 437 "original": {
354 "owner": "gkleen", 438 "owner": "gkleen",
355 "ref": "nixos-late-start-23.11", 439 "ref": "nixos-late-start-25.05",
356 "repo": "home-manager", 440 "repo": "home-manager",
357 "type": "github" 441 "type": "github"
358 } 442 }
@@ -373,10 +457,36 @@
373 "type": "github" 457 "type": "github"
374 } 458 }
375 }, 459 },
460 "lanzaboote": {
461 "inputs": {
462 "crane": "crane",
463 "flake-compat": "flake-compat_4",
464 "flake-parts": "flake-parts_3",
465 "nixpkgs": [
466 "nixpkgs"
467 ],
468 "pre-commit-hooks-nix": "pre-commit-hooks-nix_3",
469 "rust-overlay": "rust-overlay"
470 },
471 "locked": {
472 "lastModified": 1737639419,
473 "narHash": "sha256-AEEDktApTEZ5PZXNDkry2YV2k6t0dTgLPEmAZbnigXU=",
474 "owner": "nix-community",
475 "repo": "lanzaboote",
476 "rev": "a65905a09e2c43ff63be8c0e86a93712361f871e",
477 "type": "github"
478 },
479 "original": {
480 "owner": "nix-community",
481 "ref": "v0.4.2",
482 "repo": "lanzaboote",
483 "type": "github"
484 }
485 },
376 "leapseconds": { 486 "leapseconds": {
377 "flake": false, 487 "flake": false,
378 "locked": { 488 "locked": {
379 "narHash": "sha256-5ZaoY/bScQS7EGJRHu6vj9XWhbObmxNEaGugaGU7+lg=", 489 "narHash": "sha256-FJgbafPB48+5sT+7ZB8pajSsfJoISEOoaJ0d/2Ya7o8=",
380 "type": "file", 490 "type": "file",
381 "url": "https://data.iana.org/time-zones/tzdb/leap-seconds.list" 491 "url": "https://data.iana.org/time-zones/tzdb/leap-seconds.list"
382 }, 492 },
@@ -392,16 +502,16 @@
392 "nixpkgs": [ 502 "nixpkgs": [
393 "nixpkgs" 503 "nixpkgs"
394 ], 504 ],
395 "nixpkgs-stable": "nixpkgs-stable_2", 505 "nixpkgs-stable": "nixpkgs-stable_3",
396 "xwayland-satellite-stable": "xwayland-satellite-stable", 506 "xwayland-satellite-stable": "xwayland-satellite-stable",
397 "xwayland-satellite-unstable": "xwayland-satellite-unstable" 507 "xwayland-satellite-unstable": "xwayland-satellite-unstable"
398 }, 508 },
399 "locked": { 509 "locked": {
400 "lastModified": 1747115632, 510 "lastModified": 1754797984,
401 "narHash": "sha256-SypEtZQsum43HvIT4HqM1RH8CE3wCWFIO5b5IqC/2FA=", 511 "narHash": "sha256-t2WFkdB2qUyZt5rdqmJ340kqhvQWWOCJBJIc1nQ/Hg4=",
402 "owner": "sodiboo", 512 "owner": "sodiboo",
403 "repo": "niri-flake", 513 "repo": "niri-flake",
404 "rev": "44eeba852a6671ab1c7be5ca65a58c49794cef4b", 514 "rev": "647a310f1eaa59abec8aa215ff69d8979195425e",
405 "type": "github" 515 "type": "github"
406 }, 516 },
407 "original": { 517 "original": {
@@ -414,16 +524,16 @@
414 "niri-stable": { 524 "niri-stable": {
415 "flake": false, 525 "flake": false,
416 "locked": { 526 "locked": {
417 "lastModified": 1740117926, 527 "lastModified": 1748151941,
418 "narHash": "sha256-mTTHA0RAaQcdYe+9A3Jx77cmmyLFHmRoZdd8RpWa+m8=", 528 "narHash": "sha256-z4viQZLgC2bIJ3VrzQnR+q2F3gAOEQpU1H5xHtX/2fs=",
419 "owner": "YaLTeR", 529 "owner": "YaLTeR",
420 "repo": "niri", 530 "repo": "niri",
421 "rev": "b94a5db8790339cf9134873d8b490be69e02ac71", 531 "rev": "8ba57fcf25d2fc9565131684a839d58703f1dae7",
422 "type": "github" 532 "type": "github"
423 }, 533 },
424 "original": { 534 "original": {
425 "owner": "YaLTeR", 535 "owner": "YaLTeR",
426 "ref": "v25.02", 536 "ref": "v25.05.1",
427 "repo": "niri", 537 "repo": "niri",
428 "type": "github" 538 "type": "github"
429 } 539 }
@@ -431,11 +541,11 @@
431 "niri-unstable": { 541 "niri-unstable": {
432 "flake": false, 542 "flake": false,
433 "locked": { 543 "locked": {
434 "lastModified": 1747113435, 544 "lastModified": 1754742008,
435 "narHash": "sha256-9oU1mKAM2BZLSots136UA75RIed53YtYgns9TUkr3ck=", 545 "narHash": "sha256-Tp0FG7VpLudVEC622d91z2hbdfPLCXxw0Nv43iNN4O0=",
436 "owner": "YaLTeR", 546 "owner": "YaLTeR",
437 "repo": "niri", 547 "repo": "niri",
438 "rev": "6d083ea49741d6e8e85d5a1d6b6bcaa837d3b5c0", 548 "rev": "67361f88fd01974ebee4cf80f0e29c87d805cc39",
439 "type": "github" 549 "type": "github"
440 }, 550 },
441 "original": { 551 "original": {
@@ -472,11 +582,11 @@
472 ] 582 ]
473 }, 583 },
474 "locked": { 584 "locked": {
475 "lastModified": 1746934494, 585 "lastModified": 1754800038,
476 "narHash": "sha256-3n6i+F0sDASjkhbvgFDpPDZGp7z19IrRtjfF9TwJpCA=", 586 "narHash": "sha256-UbLO8/0pVBXLJuyRizYOJigtzQAj8Z2bTnbKSec/wN0=",
477 "owner": "Mic92", 587 "owner": "Mic92",
478 "repo": "nix-index-database", 588 "repo": "nix-index-database",
479 "rev": "e9b21b01e4307176b9718a29ac514838e7f6f4ff", 589 "rev": "b65f8d80656f9fcbd1fecc4b7f0730f468333142",
480 "type": "github" 590 "type": "github"
481 }, 591 },
482 "original": { 592 "original": {
@@ -514,11 +624,11 @@
514 ] 624 ]
515 }, 625 },
516 "locked": { 626 "locked": {
517 "lastModified": 1741549407, 627 "lastModified": 1748140003,
518 "narHash": "sha256-f9SXK+/rvlryDNlc++Eva/hYjbkf7OCalWwmwifRhtI=", 628 "narHash": "sha256-DNBZmuk1YRM2PmwbHzVdXumRjCUzQkMarg4iI/37rOQ=",
519 "owner": "AshleyYakeley", 629 "owner": "AshleyYakeley",
520 "repo": "NixVirt", 630 "repo": "NixVirt",
521 "rev": "9950b932dce4ae6b9bda7c83d41705c1a14e10f0", 631 "rev": "5dfe108fd859b122f9a96981cb6bc12297653d6c",
522 "type": "github" 632 "type": "github"
523 }, 633 },
524 "original": { 634 "original": {
@@ -529,11 +639,11 @@
529 }, 639 },
530 "nixos-hardware": { 640 "nixos-hardware": {
531 "locked": { 641 "locked": {
532 "lastModified": 1747083103, 642 "lastModified": 1754564048,
533 "narHash": "sha256-dMx20S2molwqJxbmMB4pGjNfgp5H1IOHNa1Eby6xL+0=", 643 "narHash": "sha256-dz303vGuzWjzOPOaYkS9xSW+B93PSAJxvBd6CambXVA=",
534 "owner": "NixOS", 644 "owner": "NixOS",
535 "repo": "nixos-hardware", 645 "repo": "nixos-hardware",
536 "rev": "d1d68fe8b00248caaa5b3bbe4984c12b47e0867d", 646 "rev": "26ed7a0d4b8741fe1ef1ee6fa64453ca056ce113",
537 "type": "github" 647 "type": "github"
538 }, 648 },
539 "original": { 649 "original": {
@@ -561,16 +671,16 @@
561 }, 671 },
562 "nixpkgs-eostre": { 672 "nixpkgs-eostre": {
563 "locked": { 673 "locked": {
564 "lastModified": 1701282334, 674 "lastModified": 1748026580,
565 "narHash": "sha256-MxCVrXY6v4QmfTwIysjjaX0XUhqBbxTWWB4HXtDYsdk=", 675 "narHash": "sha256-rWtXrcIzU5wm/C8F9LWvUfBGu5U5E7cFzPYT1pHIJaQ=",
566 "owner": "NixOS", 676 "owner": "NixOS",
567 "repo": "nixpkgs", 677 "repo": "nixpkgs",
568 "rev": "057f9aecfb71c4437d2b27d3323df7f93c010b7e", 678 "rev": "11cb3517b3af6af300dd6c055aeda73c9bf52c48",
569 "type": "github" 679 "type": "github"
570 }, 680 },
571 "original": { 681 "original": {
572 "owner": "NixOS", 682 "owner": "NixOS",
573 "ref": "23.11", 683 "ref": "25.05",
574 "repo": "nixpkgs", 684 "repo": "nixpkgs",
575 "type": "github" 685 "type": "github"
576 } 686 }
@@ -589,14 +699,17 @@
589 }, 699 },
590 "nixpkgs-lib_2": { 700 "nixpkgs-lib_2": {
591 "locked": { 701 "locked": {
592 "lastModified": 1733096140, 702 "lastModified": 1748740939,
593 "narHash": "sha256-1qRH7uAUsyQI7R1Uwl4T+XvdNv778H0Nb5njNrqvylY=", 703 "narHash": "sha256-rQaysilft1aVMwF14xIdGS3sj1yHlI6oKQNBRTF40cc=",
594 "type": "tarball", 704 "owner": "nix-community",
595 "url": "https://github.com/NixOS/nixpkgs/archive/5487e69da40cbd611ab2cadee0b4637225f7cfae.tar.gz" 705 "repo": "nixpkgs.lib",
706 "rev": "656a64127e9d791a334452c6b6606d17539476e2",
707 "type": "github"
596 }, 708 },
597 "original": { 709 "original": {
598 "type": "tarball", 710 "owner": "nix-community",
599 "url": "https://github.com/NixOS/nixpkgs/archive/5487e69da40cbd611ab2cadee0b4637225f7cfae.tar.gz" 711 "repo": "nixpkgs.lib",
712 "type": "github"
600 } 713 }
601 }, 714 },
602 "nixpkgs-lib_3": { 715 "nixpkgs-lib_3": {
@@ -651,38 +764,54 @@
651 }, 764 },
652 "nixpkgs-stable_2": { 765 "nixpkgs-stable_2": {
653 "locked": { 766 "locked": {
654 "lastModified": 1746957726, 767 "lastModified": 1730741070,
655 "narHash": "sha256-k9ut1LSfHCr0AW82ttEQzXVCqmyWVA5+SHJkS5ID/Jo=", 768 "narHash": "sha256-edm8WG19kWozJ/GqyYx2VjW99EdhjKwbY3ZwdlPAAlo=",
656 "owner": "NixOS", 769 "owner": "NixOS",
657 "repo": "nixpkgs", 770 "repo": "nixpkgs",
658 "rev": "a39ed32a651fdee6842ec930761e31d1f242cb94", 771 "rev": "d063c1dd113c91ab27959ba540c0d9753409edf3",
659 "type": "github" 772 "type": "github"
660 }, 773 },
661 "original": { 774 "original": {
662 "owner": "NixOS", 775 "owner": "NixOS",
663 "ref": "nixos-24.11", 776 "ref": "nixos-24.05",
664 "repo": "nixpkgs", 777 "repo": "nixpkgs",
665 "type": "github" 778 "type": "github"
666 } 779 }
667 }, 780 },
668 "nixpkgs-stable_3": { 781 "nixpkgs-stable_3": {
669 "locked": { 782 "locked": {
670 "lastModified": 1717179513, 783 "lastModified": 1754689972,
671 "narHash": "sha256-vboIEwIQojofItm2xGCdZCzW96U85l9nDW3ifMuAIdM=", 784 "narHash": "sha256-eogqv6FqZXHgqrbZzHnq43GalnRbLTkbBbFtEfm1RSc=",
672 "owner": "NixOS", 785 "owner": "NixOS",
673 "repo": "nixpkgs", 786 "repo": "nixpkgs",
674 "rev": "63dacb46bf939521bdc93981b4cbb7ecb58427a0", 787 "rev": "fc756aa6f5d3e2e5666efcf865d190701fef150a",
675 "type": "github" 788 "type": "github"
676 }, 789 },
677 "original": { 790 "original": {
678 "owner": "NixOS", 791 "owner": "NixOS",
679 "ref": "24.05", 792 "ref": "nixos-25.05",
680 "repo": "nixpkgs", 793 "repo": "nixpkgs",
681 "type": "github" 794 "type": "github"
682 } 795 }
683 }, 796 },
684 "nixpkgs-stable_4": { 797 "nixpkgs-stable_4": {
685 "locked": { 798 "locked": {
799 "lastModified": 1748026580,
800 "narHash": "sha256-rWtXrcIzU5wm/C8F9LWvUfBGu5U5E7cFzPYT1pHIJaQ=",
801 "owner": "NixOS",
802 "repo": "nixpkgs",
803 "rev": "11cb3517b3af6af300dd6c055aeda73c9bf52c48",
804 "type": "github"
805 },
806 "original": {
807 "owner": "NixOS",
808 "ref": "25.05",
809 "repo": "nixpkgs",
810 "type": "github"
811 }
812 },
813 "nixpkgs-stable_5": {
814 "locked": {
686 "lastModified": 1678872516, 815 "lastModified": 1678872516,
687 "narHash": "sha256-/E1YwtMtFAu2KUQKV/1+KFuReYPANM2Rzehk84VxVoc=", 816 "narHash": "sha256-/E1YwtMtFAu2KUQKV/1+KFuReYPANM2Rzehk84VxVoc=",
688 "owner": "NixOS", 817 "owner": "NixOS",
@@ -699,11 +828,11 @@
699 }, 828 },
700 "nixpkgs_2": { 829 "nixpkgs_2": {
701 "locked": { 830 "locked": {
702 "lastModified": 1746904237, 831 "lastModified": 1754725699,
703 "narHash": "sha256-3e+AVBczosP5dCLQmMoMEogM57gmZ2qrVSrmq9aResQ=", 832 "narHash": "sha256-iAcj9T/Y+3DBy2J0N+yF9XQQQ8IEb5swLFzs23CdP88=",
704 "owner": "NixOS", 833 "owner": "NixOS",
705 "repo": "nixpkgs", 834 "repo": "nixpkgs",
706 "rev": "d89fc19e405cb2d55ce7cc114356846a0ee5e956", 835 "rev": "85dbfc7aaf52ecb755f87e577ddbe6dbbdbc1054",
707 "type": "github" 836 "type": "github"
708 }, 837 },
709 "original": { 838 "original": {
@@ -811,18 +940,14 @@
811 "nixpkgs": [ 940 "nixpkgs": [
812 "ca-util", 941 "ca-util",
813 "nixpkgs" 942 "nixpkgs"
814 ],
815 "nixpkgs-stable": [
816 "ca-util",
817 "nixpkgs"
818 ] 943 ]
819 }, 944 },
820 "locked": { 945 "locked": {
821 "lastModified": 1734261738, 946 "lastModified": 1749636823,
822 "narHash": "sha256-3Lzk+7QyX8v60+km26D3dln7NMSA13vW+KYTkMkds6Q=", 947 "narHash": "sha256-WUaIlOlPLyPgz9be7fqWJA5iG6rHcGRtLERSCfUDne4=",
823 "owner": "cachix", 948 "owner": "cachix",
824 "repo": "pre-commit-hooks.nix", 949 "repo": "pre-commit-hooks.nix",
825 "rev": "4c8e75efbbdcc6f9203f64b1f21f8a55d2285264", 950 "rev": "623c56286de5a3193aa38891a6991b28f9bab056",
826 "type": "github" 951 "type": "github"
827 }, 952 },
828 "original": { 953 "original": {
@@ -833,11 +958,38 @@
833 }, 958 },
834 "pre-commit-hooks-nix_3": { 959 "pre-commit-hooks-nix_3": {
835 "inputs": { 960 "inputs": {
836 "flake-compat": "flake-compat_4", 961 "flake-compat": [
837 "flake-utils": "flake-utils_2", 962 "lanzaboote",
963 "flake-compat"
964 ],
838 "gitignore": "gitignore_3", 965 "gitignore": "gitignore_3",
966 "nixpkgs": [
967 "lanzaboote",
968 "nixpkgs"
969 ],
970 "nixpkgs-stable": "nixpkgs-stable_2"
971 },
972 "locked": {
973 "lastModified": 1731363552,
974 "narHash": "sha256-vFta1uHnD29VUY4HJOO/D6p6rxyObnf+InnSMT4jlMU=",
975 "owner": "cachix",
976 "repo": "pre-commit-hooks.nix",
977 "rev": "cd1af27aa85026ac759d5d3fccf650abe7e1bbf0",
978 "type": "github"
979 },
980 "original": {
981 "owner": "cachix",
982 "repo": "pre-commit-hooks.nix",
983 "type": "github"
984 }
985 },
986 "pre-commit-hooks-nix_4": {
987 "inputs": {
988 "flake-compat": "flake-compat_5",
989 "flake-utils": "flake-utils_2",
990 "gitignore": "gitignore_4",
839 "nixpkgs": "nixpkgs_3", 991 "nixpkgs": "nixpkgs_3",
840 "nixpkgs-stable": "nixpkgs-stable_4" 992 "nixpkgs-stable": "nixpkgs-stable_5"
841 }, 993 },
842 "locked": { 994 "locked": {
843 "lastModified": 1685361114, 995 "lastModified": 1685361114,
@@ -855,14 +1007,14 @@
855 }, 1007 },
856 "prometheus-borg-exporter": { 1008 "prometheus-borg-exporter": {
857 "inputs": { 1009 "inputs": {
858 "flake-parts": "flake-parts_3", 1010 "flake-parts": "flake-parts_4",
859 "nixpkgs": [ 1011 "nixpkgs": [
860 "nixpkgs" 1012 "nixpkgs"
861 ], 1013 ],
862 "poetry2nix": [ 1014 "poetry2nix": [
863 "poetry2nix" 1015 "poetry2nix"
864 ], 1016 ],
865 "pre-commit-hooks-nix": "pre-commit-hooks-nix_3" 1017 "pre-commit-hooks-nix": "pre-commit-hooks-nix_4"
866 }, 1018 },
867 "locked": { 1019 "locked": {
868 "lastModified": 1722088088, 1020 "lastModified": 1722088088,
@@ -882,21 +1034,50 @@
882 "pyproject-build-systems": { 1034 "pyproject-build-systems": {
883 "inputs": { 1035 "inputs": {
884 "nixpkgs": [ 1036 "nixpkgs": [
1037 "ca-util",
885 "nixpkgs" 1038 "nixpkgs"
886 ], 1039 ],
887 "pyproject-nix": [ 1040 "pyproject-nix": [
1041 "ca-util",
888 "pyproject-nix" 1042 "pyproject-nix"
889 ], 1043 ],
890 "uv2nix": [ 1044 "uv2nix": [
1045 "ca-util",
891 "uv2nix" 1046 "uv2nix"
892 ] 1047 ]
893 }, 1048 },
894 "locked": { 1049 "locked": {
895 "lastModified": 1744599653, 1050 "lastModified": 1749519371,
896 "narHash": "sha256-nysSwVVjG4hKoOjhjvE6U5lIKA8sEr1d1QzEfZsannU=", 1051 "narHash": "sha256-UJONN7mA2stweZCoRcry2aa1XTTBL0AfUOY84Lmqhos=",
897 "owner": "pyproject-nix", 1052 "owner": "pyproject-nix",
898 "repo": "build-system-pkgs", 1053 "repo": "build-system-pkgs",
899 "rev": "7dba6dbc73120e15b558754c26024f6c93015dd7", 1054 "rev": "7c06967eca687f3482624250428cc12f43c92523",
1055 "type": "github"
1056 },
1057 "original": {
1058 "owner": "pyproject-nix",
1059 "repo": "build-system-pkgs",
1060 "type": "github"
1061 }
1062 },
1063 "pyproject-build-systems_2": {
1064 "inputs": {
1065 "nixpkgs": [
1066 "nixpkgs"
1067 ],
1068 "pyproject-nix": [
1069 "pyproject-nix"
1070 ],
1071 "uv2nix": [
1072 "uv2nix"
1073 ]
1074 },
1075 "locked": {
1076 "lastModified": 1753063383,
1077 "narHash": "sha256-H+gLv6424OjJSD+l1OU1ejxkN/v0U+yaoQdh2huCXYI=",
1078 "owner": "pyproject-nix",
1079 "repo": "build-system-pkgs",
1080 "rev": "45888b7fd4bf36c57acc55f07917bdf49ec89ec9",
900 "type": "github" 1081 "type": "github"
901 }, 1082 },
902 "original": { 1083 "original": {
@@ -912,11 +1093,11 @@
912 ] 1093 ]
913 }, 1094 },
914 "locked": { 1095 "locked": {
915 "lastModified": 1746540146, 1096 "lastModified": 1754287816,
916 "narHash": "sha256-QxdHGNpbicIrw5t6U3x+ZxeY/7IEJ6lYbvsjXmcxFIM=", 1097 "narHash": "sha256-kDt0HB89oWTlTQMnTwDxx3BlRHK1AdAJ1kMcVYGuccs=",
917 "owner": "pyproject-nix", 1098 "owner": "pyproject-nix",
918 "repo": "pyproject.nix", 1099 "repo": "pyproject.nix",
919 "rev": "e09c10c24ebb955125fda449939bfba664c467fd", 1100 "rev": "efe944d0902f406c28b4e8662312292a37e4de87",
920 "type": "github" 1101 "type": "github"
921 }, 1102 },
922 "original": { 1103 "original": {
@@ -936,6 +1117,7 @@
936 "home-manager": "home-manager", 1117 "home-manager": "home-manager",
937 "home-manager-eostre": "home-manager-eostre", 1118 "home-manager-eostre": "home-manager-eostre",
938 "impermanence": "impermanence", 1119 "impermanence": "impermanence",
1120 "lanzaboote": "lanzaboote",
939 "niri-flake": "niri-flake", 1121 "niri-flake": "niri-flake",
940 "nix-index-database": "nix-index-database", 1122 "nix-index-database": "nix-index-database",
941 "nix-monitored": "nix-monitored", 1123 "nix-monitored": "nix-monitored",
@@ -944,17 +1126,38 @@
944 "nixpkgs": "nixpkgs_2", 1126 "nixpkgs": "nixpkgs_2",
945 "nixpkgs-eostre": "nixpkgs-eostre", 1127 "nixpkgs-eostre": "nixpkgs-eostre",
946 "nixpkgs-pgbackrest": "nixpkgs-pgbackrest", 1128 "nixpkgs-pgbackrest": "nixpkgs-pgbackrest",
947 "nixpkgs-stable": "nixpkgs-stable_3", 1129 "nixpkgs-stable": "nixpkgs-stable_4",
948 "nvfetcher": "nvfetcher", 1130 "nvfetcher": "nvfetcher",
949 "poetry2nix": "poetry2nix", 1131 "poetry2nix": "poetry2nix",
950 "prometheus-borg-exporter": "prometheus-borg-exporter", 1132 "prometheus-borg-exporter": "prometheus-borg-exporter",
951 "pyproject-build-systems": "pyproject-build-systems", 1133 "pyproject-build-systems": "pyproject-build-systems_2",
952 "pyproject-nix": "pyproject-nix", 1134 "pyproject-nix": "pyproject-nix",
953 "sops-nix": "sops-nix", 1135 "sops-nix": "sops-nix",
954 "uv2nix": "uv2nix", 1136 "uv2nix": "uv2nix",
955 "waybar": "waybar" 1137 "waybar": "waybar"
956 } 1138 }
957 }, 1139 },
1140 "rust-overlay": {
1141 "inputs": {
1142 "nixpkgs": [
1143 "lanzaboote",
1144 "nixpkgs"
1145 ]
1146 },
1147 "locked": {
1148 "lastModified": 1731897198,
1149 "narHash": "sha256-Ou7vLETSKwmE/HRQz4cImXXJBr/k9gp4J4z/PF8LzTE=",
1150 "owner": "oxalica",
1151 "repo": "rust-overlay",
1152 "rev": "0be641045af6d8666c11c2c40e45ffc9667839b5",
1153 "type": "github"
1154 },
1155 "original": {
1156 "owner": "oxalica",
1157 "repo": "rust-overlay",
1158 "type": "github"
1159 }
1160 },
958 "sops-nix": { 1161 "sops-nix": {
959 "inputs": { 1162 "inputs": {
960 "nixpkgs": [ 1163 "nixpkgs": [
@@ -962,11 +1165,11 @@
962 ] 1165 ]
963 }, 1166 },
964 "locked": { 1167 "locked": {
965 "lastModified": 1746485181, 1168 "lastModified": 1754328224,
966 "narHash": "sha256-PxrrSFLaC7YuItShxmYbMgSuFFuwxBB+qsl9BZUnRvg=", 1169 "narHash": "sha256-glPK8DF329/dXtosV7YSzRlF4n35WDjaVwdOMEoEXHA=",
967 "owner": "Mic92", 1170 "owner": "Mic92",
968 "repo": "sops-nix", 1171 "repo": "sops-nix",
969 "rev": "e93ee1d900ad264d65e9701a5c6f895683433386", 1172 "rev": "49021900e69812ba7ddb9e40f9170218a7eca9f4",
970 "type": "github" 1173 "type": "github"
971 }, 1174 },
972 "original": { 1175 "original": {
@@ -1037,11 +1240,11 @@
1037 ] 1240 ]
1038 }, 1241 },
1039 "locked": { 1242 "locked": {
1040 "lastModified": 1746649034, 1243 "lastModified": 1754719699,
1041 "narHash": "sha256-gmv+ZiY3pQnwgI0Gm3Z1tNSux1CnOJ0De+xeDOol1+0=", 1244 "narHash": "sha256-Ek6WILkhVMDWm9j0L23gxHhDt62z1+A68D+pPt0ghao=",
1042 "owner": "pyproject-nix", 1245 "owner": "pyproject-nix",
1043 "repo": "uv2nix", 1246 "repo": "uv2nix",
1044 "rev": "fe540e91c26f378c62bf6da365a97e848434d0cd", 1247 "rev": "38ead6a16ba1be029dd40fe17fc064d6934847d2",
1045 "type": "github" 1248 "type": "github"
1046 }, 1249 },
1047 "original": { 1250 "original": {
@@ -1060,11 +1263,11 @@
1060 ] 1263 ]
1061 }, 1264 },
1062 "locked": { 1265 "locked": {
1063 "lastModified": 1747383113, 1266 "lastModified": 1752562190,
1064 "narHash": "sha256-/YW7eOKU3gsNplxvUDpEj1LiXtcCENSFpS1c8kXSDWw=", 1267 "narHash": "sha256-zWOMCNe56H2PHUd3rJZ6tklZUZBLgRo85jd9IlK1g9o=",
1065 "owner": "gkleen", 1268 "owner": "gkleen",
1066 "repo": "Waybar", 1269 "repo": "Waybar",
1067 "rev": "919036587381595f15010ac95644992fe6d7343d", 1270 "rev": "d008cd998369c40f2344a856caf39cdbbd7bd068",
1068 "type": "github" 1271 "type": "github"
1069 }, 1272 },
1070 "original": { 1273 "original": {
@@ -1077,16 +1280,16 @@
1077 "xwayland-satellite-stable": { 1280 "xwayland-satellite-stable": {
1078 "flake": false, 1281 "flake": false,
1079 "locked": { 1282 "locked": {
1080 "lastModified": 1739246919, 1283 "lastModified": 1748488455,
1081 "narHash": "sha256-/hBM43/Gd0/tW+egrhlWgOIISeJxEs2uAOIYVpfDKeU=", 1284 "narHash": "sha256-IiLr1alzKFIy5tGGpDlabQbe6LV1c9ABvkH6T5WmyRI=",
1082 "owner": "Supreeeme", 1285 "owner": "Supreeeme",
1083 "repo": "xwayland-satellite", 1286 "repo": "xwayland-satellite",
1084 "rev": "44590a416d4a3e8220e19e29e0b6efe64a80315d", 1287 "rev": "3ba30b149f9eb2bbf42cf4758d2158ca8cceef73",
1085 "type": "github" 1288 "type": "github"
1086 }, 1289 },
1087 "original": { 1290 "original": {
1088 "owner": "Supreeeme", 1291 "owner": "Supreeeme",
1089 "ref": "v0.5.1", 1292 "ref": "v0.6",
1090 "repo": "xwayland-satellite", 1293 "repo": "xwayland-satellite",
1091 "type": "github" 1294 "type": "github"
1092 } 1295 }
@@ -1094,11 +1297,11 @@
1094 "xwayland-satellite-unstable": { 1297 "xwayland-satellite-unstable": {
1095 "flake": false, 1298 "flake": false,
1096 "locked": { 1299 "locked": {
1097 "lastModified": 1747111562, 1300 "lastModified": 1754533920,
1098 "narHash": "sha256-GAqhWoxaBIk0tgoecZPa8gTHDHxNc0JtlwWHZN2iOOo=", 1301 "narHash": "sha256-fCZ68Yud1sUCq6UNXj0SDyiBgVA8gJUE+14ZFGsFJG8=",
1099 "owner": "Supreeeme", 1302 "owner": "Supreeeme",
1100 "repo": "xwayland-satellite", 1303 "repo": "xwayland-satellite",
1101 "rev": "ec9ff64c1e0cbec42710b580b7c0f759b1694e72", 1304 "rev": "e0d1dad25a158551ab58547b2ece4b7d5a19929c",
1102 "type": "github" 1305 "type": "github"
1103 }, 1306 },
1104 "original": { 1307 "original": {
diff --git a/flake.nix b/flake.nix
index 039dee1f..b9382c6f 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 };
@@ -145,20 +145,23 @@
145 type = "gitlab"; 145 type = "gitlab";
146 owner = "gkleen"; 146 owner = "gkleen";
147 repo = "ca"; 147 repo = "ca";
148 ref = "v3.1.3"; 148 ref = "v3.1.5";
149 inputs = { 149 inputs = {
150 pyproject-nix.follows = "pyproject-nix";
151 uv2nix.follows = "uv2nix";
150 nixpkgs.follows = "nixpkgs"; 152 nixpkgs.follows = "nixpkgs";
151 poetry2nix.follows = "poetry2nix";
152 }; 153 };
153 }; 154 };
154 backup-utils = { 155 backup-utils = {
155 type = "gitlab"; 156 type = "gitlab";
156 owner = "gkleen"; 157 owner = "gkleen";
157 repo = "backup-utils"; 158 repo = "backup-utils";
158 ref = "v0.1.6"; 159 ref = "v0.1.7";
159 inputs = { 160 inputs = {
160 nixpkgs.follows = "nixpkgs"; 161 nixpkgs.follows = "nixpkgs";
161 poetry2nix.follows = "poetry2nix"; 162 pyproject-nix.follows = "pyproject-nix";
163 uv2nix.follows = "uv2nix";
164 pyproject-build-systems.follows = "pyproject-build-systems";
162 }; 165 };
163 }; 166 };
164 prometheus-borg-exporter = { 167 prometheus-borg-exporter = {
@@ -218,6 +221,14 @@
218 nixpkgs.follows = "nixpkgs"; 221 nixpkgs.follows = "nixpkgs";
219 }; 222 };
220 }; 223 };
224 lanzaboote = {
225 type = "github";
226 owner = "nix-community";
227 repo = "lanzaboote";
228 ref = "v0.4.2";
229
230 inputs.nixpkgs.follows = "nixpkgs";
231 };
221 }; 232 };
222 233
223 outputs = { self, nixpkgs, home-manager, sops-nix, deploy-rs, nvfetcher, niri-flake, ... }@inputs: 234 outputs = { self, nixpkgs, home-manager, sops-nix, deploy-rs, nvfetcher, niri-flake, ... }@inputs:
@@ -363,7 +374,7 @@
363 374
364 overlays = mapAttrs (_name: path: mkOverlay path) overlayPaths; 375 overlays = mapAttrs (_name: path: mkOverlay path) overlayPaths;
365 376
366 packages = forAllSystems (system: systemPkgs: nixImport rec { dir = ./tools; _import = _path: name: import "${toString dir}/${name}" ({ inherit system; } // inputs); }); 377 packages = forAllSystems (system: systemPkgs: nixImport rec { dir = ./tools; _import = name: _base: import (dir + "/${name}") ({ inherit system; } // inputs); });
367 378
368 # packages = mapAttrs (_name: filterAttrs (_name: isDerivation)) packages; 379 # packages = mapAttrs (_name: filterAttrs (_name: isDerivation)) packages;
369 # packages' = mapAttrs (_name: filterAttrs (_name: value: !(isDerivation value))) packages; 380 # packages' = mapAttrs (_name: filterAttrs (_name: value: !(isDerivation value))) packages;
@@ -375,6 +386,8 @@
375 activateNixosConfigurations activateHomeManagerConfigurations 386 activateNixosConfigurations activateHomeManagerConfigurations
376 ]; 387 ];
377 388
389 lib = nixImport rec { dir = ./lib; _import = name: _base: import (dir + "/${name}") inputs; };
390
378 devShells = forAllSystems (system: systemPkgs: { default = import ./shell.nix ({ inherit system; } // inputs); } // installerShells system systemPkgs); 391 devShells = forAllSystems (system: systemPkgs: { default = import ./shell.nix ({ inherit system; } // inputs); } // installerShells system systemPkgs);
379 392
380 templates.default = { 393 templates.default = {
@@ -398,7 +411,7 @@
398 # path = activateHomeManager (self.nixosConfigurations.${hostname}.config.nixpkgs.system) usercfg.home; 411 # path = activateHomeManager (self.nixosConfigurations.${hostname}.config.nixpkgs.system) usercfg.home;
399 # }) self.nixosConfigurations.${hostname}.config.home-manager.users); 412 # }) self.nixosConfigurations.${hostname}.config.home-manager.users);
400 }) (nixImport { dir = ./hosts; _import = (_path: name: name); }); 413 }) (nixImport { dir = ./hosts; _import = (_path: name: name); });
401 overrides = if pathExists ./deploy then nixImport { dir = ./deploy; _import = path: _name: import (./deploy + "/${path}") inputs; } else {}; 414 overrides = if pathExists ./deploy then nixImport rec { dir = ./deploy; _import = path: _name: import (dir + "/${path}") inputs; } else {};
402 filterEnabled = attrs: mapAttrs (_n: v: filterAttrs (n: _v: n != "enabled") v) (filterAttrs (_n: v: v.enabled or true) attrs); 415 filterEnabled = attrs: mapAttrs (_n: v: filterAttrs (n: _v: n != "enabled") v) (filterAttrs (_n: v: v.enabled or true) attrs);
403 in mapAttrs (_n: v: if v ? "profiles" then v // { profiles = filterEnabled v.profiles; } else v) (filterEnabled (recursiveUpdate defaults overrides)); 416 in mapAttrs (_n: v: if v ? "profiles" then v // { profiles = filterEnabled v.profiles; } else v) (filterEnabled (recursiveUpdate defaults overrides));
404 417
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/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 f4de24e8..b436542f 100644
--- a/hosts/sif/default.nix
+++ b/hosts/sif/default.nix
@@ -12,8 +12,8 @@ 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 lanzaboote
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
19 flakeInputs.nixVirt.nixosModules.default 19 flakeInputs.nixVirt.nixosModules.default
@@ -34,6 +34,10 @@ in {
34 initrd = { 34 initrd = {
35 systemd = { 35 systemd = {
36 emergencyAccess = config.users.users.root.hashedPassword; 36 emergencyAccess = config.users.users.root.hashedPassword;
37 extraBin = {
38 "vim" = lib.getExe pkgs.vim;
39 "grep" = lib.getExe pkgs.gnugrep;
40 };
37 }; 41 };
38 luks.devices = { 42 luks.devices = {
39 nvm0 = { device = "/dev/disk/by-uuid/bef17e86-d929-4a60-97cb-6bfa133face7"; bypassWorkqueues = true; }; 43 nvm0 = { device = "/dev/disk/by-uuid/bef17e86-d929-4a60-97cb-6bfa133face7"; bypassWorkqueues = true; };
@@ -47,13 +51,8 @@ in {
47 51
48 blacklistedKernelModules = [ "nouveau" ]; 52 blacklistedKernelModules = [ "nouveau" ];
49 53
50 # Use the systemd-boot EFI boot loader. 54 lanzaboote.configurationLimit = 15;
51 loader = { 55 loader = {
52 systemd-boot = {
53 enable = true;
54 configurationLimit = 15;
55 netbootxyz.enable = true;
56 };
57 efi.canTouchEfiVariables = true; 56 efi.canTouchEfiVariables = true;
58 timeout = null; 57 timeout = null;
59 }; 58 };
@@ -77,6 +76,14 @@ in {
77 }; 76 };
78 } 77 }
79 ]; 78 ];
79 consoleLogLevel = 3;
80 kernelParams = [
81 "quiet"
82 "boot.shell_on_fail"
83 "udev.log_priority=3"
84 "rd.systemd.show_status=auto"
85 "plymouth.use-simpledrm"
86 ];
80 87
81 tmp.useTmpfs = true; 88 tmp.useTmpfs = true;
82 89
@@ -98,6 +105,8 @@ in {
98 server ptbtime2.ptb.de prefer iburst nts 105 server ptbtime2.ptb.de prefer iburst nts
99 server ptbtime3.ptb.de prefer iburst nts 106 server ptbtime3.ptb.de prefer iburst nts
100 server ptbtime4.ptb.de prefer iburst nts 107 server ptbtime4.ptb.de prefer iburst nts
108 pool ntppool1.time.nl prefer iburst nts
109 pool ntppool2.time.nl prefer iburst nts
101 110
102 authselectmode require 111 authselectmode require
103 minsources 3 112 minsources 3
@@ -130,6 +139,12 @@ in {
130 useNetworkd = true; 139 useNetworkd = true;
131 }; 140 };
132 141
142 environment.etc."NetworkManager/dnsmasq.d/dnssec.conf" = {
143 text = ''
144 conf-file=${pkgs.dnsmasq}/share/dnsmasq/trust-anchors.conf
145 dnssec
146 '';
147 };
133 environment.etc."NetworkManager/dnsmasq.d/libvirt_dnsmasq.conf" = { 148 environment.etc."NetworkManager/dnsmasq.d/libvirt_dnsmasq.conf" = {
134 text = '' 149 text = ''
135 except-interface=virbr0 150 except-interface=virbr0
@@ -372,19 +387,6 @@ in {
372 ]; 387 ];
373 388
374 services = { 389 services = {
375 uucp = {
376 enable = true;
377 nodeName = "sif";
378 remoteNodes = {
379 "ymir" = {
380 publicKeys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG6KNtsCOl5fsZ4rV7udTulGMphJweLBoKapzerWNoLY root@ymir"];
381 hostnames = ["ymir.yggdrasil.li" "ymir.niflheim.yggdrasil"];
382 };
383 };
384
385 defaultCommands = lib.mkForce [];
386 };
387
388 avahi.enable = true; 390 avahi.enable = true;
389 391
390 fwupd.enable = true; 392 fwupd.enable = true;
@@ -403,8 +405,8 @@ in {
403 405
404 logind = { 406 logind = {
405 lidSwitch = "suspend"; 407 lidSwitch = "suspend";
406 lidSwitchDocked = "lock"; 408 lidSwitchDocked = "ignore";
407 lidSwitchExternalPower = "lock"; 409 lidSwitchExternalPower = "ignore";
408 }; 410 };
409 411
410 atd = { 412 atd = {
@@ -446,11 +448,6 @@ in {
446 448
447 systemd.tmpfiles.settings = { 449 systemd.tmpfiles.settings = {
448 "10-localtime"."/etc/localtime".L.argument = "/.bcachefs/etc/localtime"; 450 "10-localtime"."/etc/localtime".L.argument = "/.bcachefs/etc/localtime";
449
450 # "10-regreet"."/var/cache/regreet/cache.toml".C.argument = toString ((pkgs.formats.toml {}).generate "cache.toml" {
451 # last_user = "gkleen";
452 # user_to_last_sess.gkleen = "Niri";
453 # });
454 }; 451 };
455 452
456 users = { 453 users = {
@@ -610,25 +607,6 @@ in {
610 607
611 environment.etc."X11/xorg.conf.d/50-wacom.conf".source = lib.mkForce ./wacom.conf; 608 environment.etc."X11/xorg.conf.d/50-wacom.conf".source = lib.mkForce ./wacom.conf;
612 609
613 systemd.services."ac-plugged" = {
614 description = "Inhibit handling of lid-switch and sleep";
615
616 path = with pkgs; [ systemd coreutils ];
617
618 script = ''
619 exec systemd-inhibit --what=handle-lid-switch --why="AC is connected" --mode=block sleep infinity
620 '';
621
622 serviceConfig = {
623 Type = "simple";
624 };
625 };
626
627 services.udev.extraRules = with pkgs; lib.mkAfter ''
628 SUBSYSTEM=="power_supply", ENV{POWER_SUPPLY_ONLINE}=="0", RUN+="${systemd}/bin/systemctl --no-block stop ac-plugged.service"
629 SUBSYSTEM=="power_supply", ENV{POWER_SUPPLY_ONLINE}=="1", RUN+="${systemd}/bin/systemctl --no-block start ac-plugged.service"
630 '';
631
632 systemd.services."nix-daemon".serviceConfig = { 610 systemd.services."nix-daemon".serviceConfig = {
633 MemoryAccounting = true; 611 MemoryAccounting = true;
634 MemoryHigh = "50%"; 612 MemoryHigh = "50%";
@@ -688,7 +666,7 @@ in {
688 directories = [ 666 directories = [
689 "/nix" 667 "/nix"
690 "/root" 668 "/root"
691 "/home" 669 "/home"
692 "/var/log" 670 "/var/log"
693 "/var/lib/sops-nix" 671 "/var/lib/sops-nix"
694 "/var/lib/nixos" 672 "/var/lib/nixos"
@@ -698,9 +676,9 @@ in {
698 "/var/lib/bluetooth" 676 "/var/lib/bluetooth"
699 "/var/lib/upower" 677 "/var/lib/upower"
700 "/var/lib/postfix" 678 "/var/lib/postfix"
679 "/var/lib/regreet"
701 "/etc/NetworkManager/system-connections" 680 "/etc/NetworkManager/system-connections"
702 { directory = "/var/uucp"; user = "uucp"; group = "uucp"; mode = "0700"; } 681 config.boot.lanzaboote.pkiBundle
703 { directory = "/var/spool/uucp"; user = "uucp"; group = "uucp"; mode = "0750"; }
704 ]; 682 ];
705 files = [ 683 files = [
706 ]; 684 ];
diff --git a/hosts/sif/email/default.nix b/hosts/sif/email/default.nix
new file mode 100644
index 00000000..bebf7980
--- /dev/null
+++ b/hosts/sif/email/default.nix
@@ -0,0 +1,111 @@
1{ config, lib, pkgs, ... }:
2{
3 services.postfix = {
4 enable = true;
5 enableSmtp = false;
6 enableSubmission = false;
7 setSendmail = true;
8 # networksStyle = "host";
9 settings.main = {
10 recpipient_delimiter = "+";
11 mydestination = [];
12 myhostname = "sif.midgard.yggdrasil";
13
14 mydomain = "yggdrasil.li";
15
16 local_transport = "error:5.1.1 No local delivery";
17 alias_database = [];
18 alias_maps = [];
19 local_recipient_maps = [];
20
21 inet_interfaces = "loopback-only";
22
23 message_size_limit = 0;
24
25 authorized_submit_users = "inline:{ gkleen= }";
26 authorized_flush_users = "inline:{ gkleen= }";
27 authorized_mailq_users = "inline:{ gkleen= }";
28
29 smtp_generic_maps = "inline:{ root=root+sif }";
30
31 mynetworks = ["127.0.0.0/8" "[::1]/128"];
32 smtpd_client_restrictions = ["permit_mynetworks" "reject"];
33 smtpd_relay_restrictions = ["permit_mynetworks" "reject"];
34
35 sender_dependent_default_transport_maps = ''regexp:${pkgs.writeText "sender_relay" ''
36 /@(cip|stud)\.ifi\.(lmu|uni-muenchen)\.de$/ smtp:smtp.ifi.lmu.de
37 /@ifi\.(lmu|uni-muenchen)\.de$/ smtp:smtpin1.ifi.lmu.de:587
38 /@math(ematik)?\.(lmu|uni-muenchen)\.de$/ smtps:smtp.math.lmu.de:465
39 /@(campus\.)?lmu\.de$/ smtp:postout.lrz.de
40 ''}'';
41 sender_bcc_maps = ''regexp:${pkgs.writeText "sender_bcc" ''
42 /^uni2work(-[^@]*)?@ifi\.lmu\.de$/ uni2work@ifi.lmu.de
43 /@ifi\.lmu\.de$/ gregor.kleen@ifi.lmu.de
44 ''}'';
45 relayhost = ["[surtr.yggdrasil.li]:465"];
46 default_transport = "relay";
47
48 smtp_sasl_auth_enable = true;
49 smtp_sender_dependent_authentication = true;
50 smtp_sasl_tls_security_options = "noanonymous";
51 smtp_sasl_mechanism_filter = ["plain"];
52 smtp_sasl_password_maps = "regexp:/run/credentials/postfix.service/sasl_passwd";
53 smtp_cname_overrides_servername = false;
54 smtp_always_send_ehlo = true;
55 smtp_tls_security_level = "dane";
56
57 smtp_tls_loglevel = "1";
58 smtp_dns_support_level = "dnssec";
59 };
60 settings.master = {
61 submission = {
62 type = "inet";
63 private = false;
64 command = "smtpd";
65 args = [
66 "-o" "syslog_name=postfix/$service_name"
67 ];
68 };
69 smtp = { };
70 smtps = {
71 type = "unix";
72 private = true;
73 privileged = true;
74 chroot = false;
75 command = "smtp";
76 args = [
77 "-o" "smtp_tls_wrappermode=yes"
78 "-o" "smtp_tls_security_level=encrypt"
79 ];
80 };
81 relay = {
82 command = "smtp";
83 args = [
84 "-o" "smtp_fallback_relay="
85 "-o" "smtp_tls_security_level=verify"
86 "-o" "smtp_tls_wrappermode=yes"
87 "-o" "smtp_tls_cert_file=${./relay.crt}"
88 "-o" "smtp_tls_key_file=/run/credentials/postfix.service/relay.key"
89 ];
90 };
91 };
92 };
93
94 systemd.services.postfix = {
95 serviceConfig.LoadCredential = [
96 "sasl_passwd:${config.sops.secrets."postfix-sasl-passwd".path}"
97 "relay.key:${config.sops.secrets."relay-key".path}"
98 ];
99 };
100
101 sops.secrets = {
102 postfix-sasl-passwd = {
103 key = "sasl-passwd";
104 sopsFile = ./secrets.yaml;
105 };
106 relay-key = {
107 format = "binary";
108 sopsFile = ./relay.key;
109 };
110 };
111}
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/hw.nix b/hosts/sif/hw.nix
index 1bcf0261..e567c37d 100644
--- a/hosts/sif/hw.nix
+++ b/hosts/sif/hw.nix
@@ -25,7 +25,7 @@
25 # system.etc.overlay.enable = false; 25 # system.etc.overlay.enable = false;
26 26
27 boot.initrd.systemd.packages = [ 27 boot.initrd.systemd.packages = [
28 (pkgs.writeTextDir "/etc/systemd/system/\\x2ebcachefs.mount.d/block_scan.conf" '' 28 (pkgs.writeTextDir "/etc/systemd/system/sysroot-.bcachefs.mount.d/block_scan.conf" ''
29 [Mount] 29 [Mount]
30 Environment=BCACHEFS_BLOCK_SCAN=1 30 Environment=BCACHEFS_BLOCK_SCAN=1
31 '') 31 '')
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/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 d420040a..63beece3 100644
--- a/hosts/surtr/default.nix
+++ b/hosts/surtr/default.nix
@@ -7,7 +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 10 ./paperless.nix ./hledger.nix ./audiobookshelf.nix ./kimai.nix
11 ]; 11 ];
12 12
13 config = { 13 config = {
@@ -22,7 +22,6 @@ with lib;
22 device = "/dev/vda"; 22 device = "/dev/vda";
23 }; 23 };
24 24
25
26 tmp.useTmpfs = true; 25 tmp.useTmpfs = true;
27 26
28 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
diff --git a/hosts/surtr/dns/default.nix b/hosts/surtr/dns/default.nix
index 7aa3fb00..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" "paperless.yggdrasil.li" "hledger.yggdrasil.li" "audiobookshelf.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"]; };
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/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 ab117f09..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 2025020900 ; 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.
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.yggdrasil.soa b/hosts/surtr/dns/zones/li.yggdrasil.soa
index 7273827b..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 2025050900 ; serial 4 2025060700 ; serial
5 10800 ; refresh 5 10800 ; refresh
6 3600 ; retry 6 3600 ; retry
7 604800 ; expire 7 604800 ; expire
@@ -101,12 +101,22 @@ _acme-challenge.audiobookshelf IN NS ns.yggdrasil.li.
101 101
102audiobookshelf IN HTTPS 1 . alpn="h2,h3" ipv4hint="202.61.241.61" ipv6hint="2a03:4000:52:ada::" 102audiobookshelf IN HTTPS 1 . alpn="h2,h3" ipv4hint="202.61.241.61" ipv6hint="2a03:4000:52:ada::"
103 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
104vidhar IN AAAA 2a03:4000:52:ada:4:1:: 112vidhar IN AAAA 2a03:4000:52:ada:4:1::
105vidhar IN MX 0 ymir.yggdrasil.li 113vidhar IN MX 0 ymir.yggdrasil.li
106vidhar IN TXT "v=spf1 redirect=yggdrasil.li" 114vidhar IN TXT "v=spf1 redirect=yggdrasil.li"
107 115
108mailout IN A 188.68.51.254 116mailout IN A 188.68.51.254
109mailout 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::
110mailout IN MX 0 ymir.yggdrasil.li 120mailout IN MX 0 ymir.yggdrasil.li
111mailout IN TXT "v=spf1 redirect=yggdrasil.li" 121mailout IN TXT "v=spf1 redirect=yggdrasil.li"
112 122
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 845f6455..a3e06ca6 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
@@ -108,19 +124,20 @@ in {
108 services.postfix = { 124 services.postfix = {
109 enable = true; 125 enable = true;
110 enableSmtp = false; 126 enableSmtp = false;
111 hostname = "surtr.yggdrasil.li";
112 recipientDelimiter = "";
113 setSendmail = true; 127 setSendmail = true;
114 postmasterAlias = ""; rootAlias = ""; extraAliases = ""; 128 postmasterAlias = ""; rootAlias = ""; extraAliases = "";
115 destination = []; 129 settings.main = {
116 sslCert = "/run/credentials/postfix.service/surtr.yggdrasil.li.pem"; 130 recpipient_delimiter = "";
117 sslKey = "/run/credentials/postfix.service/surtr.yggdrasil.li.key.pem"; 131 mydestination = [];
118 networks = []; 132 mynetworks = [];
119 config = let 133 myhostname = "surtr.yggdrasil.li";
120 relay_ccert = "texthash:${pkgs.writeText "relay_ccert" ""}"; 134
121 in {
122 smtpd_tls_security_level = "may"; 135 smtpd_tls_security_level = "may";
123 136
137 smtpd_tls_chain_files = [
138 "/run/credentials/postfix.service/surtr.yggdrasil.li.full.pem"
139 ];
140
124 #the dh params 141 #the dh params
125 smtpd_tls_dh1024_param_file = toString config.security.dhparams.params."postfix-1024".path; 142 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; 143 smtpd_tls_dh512_param_file = toString config.security.dhparams.params."postfix-512".path;
@@ -155,21 +172,14 @@ in {
155 172
156 smtp_tls_connection_reuse = true; 173 smtp_tls_connection_reuse = true;
157 174
158 tls_server_sni_maps = ''texthash:${pkgs.writeText "sni" ( 175 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 176
165 smtp_tls_policy_maps = "socketmap:unix:${config.services.postfix-mta-sts-resolver.settings.path}:postfix"; 177 smtp_tls_policy_maps = "socketmap:unix:${config.services.postfix-mta-sts-resolver.settings.path}:postfix";
166 178
167 local_recipient_maps = ""; 179 local_recipient_maps = "";
168 180
169 # 10 GiB 181 message_size_limit = 10 * 1024 * 1024 * 1024;
170 message_size_limit = "10737418240"; 182 mailbox_size_limit = 10 * 1024 * 1024 * 1024;
171 # 10 GiB
172 mailbox_size_limit = "10737418240";
173 183
174 smtpd_delay_reject = true; 184 smtpd_delay_reject = true;
175 smtpd_helo_required = true; 185 smtpd_helo_required = true;
@@ -184,12 +194,12 @@ in {
184 dbname = email 194 dbname = email
185 query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s' 195 query = SELECT action FROM virtual_mailbox_access WHERE lookup = '%s'
186 ''}" 196 ''}"
187 "check_ccert_access ${relay_ccert}"
188 "reject_non_fqdn_helo_hostname" 197 "reject_non_fqdn_helo_hostname"
189 "reject_invalid_helo_hostname" 198 "reject_invalid_helo_hostname"
190 "reject_unauth_destination" 199 "reject_unauth_destination"
191 "reject_unknown_recipient_domain" 200 "reject_unknown_recipient_domain"
192 "reject_unverified_recipient" 201 "reject_unverified_recipient"
202 "check_policy_service unix:/run/postfix-internal-policy.sock"
193 ]; 203 ];
194 unverified_recipient_reject_code = "550"; 204 unverified_recipient_reject_code = "550";
195 unverified_recipient_reject_reason = "Recipient address lookup failed"; 205 unverified_recipient_reject_reason = "Recipient address lookup failed";
@@ -204,7 +214,6 @@ in {
204 address_verify_sender_ttl = "30045s"; 214 address_verify_sender_ttl = "30045s";
205 215
206 smtpd_relay_restrictions = [ 216 smtpd_relay_restrictions = [
207 "check_ccert_access ${relay_ccert}"
208 "reject_unauth_destination" 217 "reject_unauth_destination"
209 ]; 218 ];
210 219
@@ -251,13 +260,26 @@ in {
251 virtual_transport = "dvlmtp:unix:/run/dovecot-lmtp"; 260 virtual_transport = "dvlmtp:unix:/run/dovecot-lmtp";
252 smtputf8_enable = false; 261 smtputf8_enable = false;
253 262
254 authorized_submit_users = "inline:{ root= postfwd= }"; 263 authorized_submit_users = "inline:{ root= postfwd= ${config.services.dovecot2.user}= }";
264 authorized_flush_users = "inline:{ root= }";
265 authorized_mailq_users = "inline:{ root= }";
255 266
256 postscreen_access_list = ""; 267 postscreen_access_list = "";
257 postscreen_denylist_action = "drop"; 268 postscreen_denylist_action = "drop";
258 postscreen_greet_action = "enforce"; 269 postscreen_greet_action = "enforce";
270
271 sender_bcc_maps = ''pgsql:${pkgs.writeText "sender_bcc_maps.cf" ''
272 hosts = postgresql:///email
273 dbname = email
274 query = SELECT value FROM sender_bcc_maps WHERE key = '%s'
275 ''}'';
276 recipient_bcc_maps = ''pgsql:${pkgs.writeText "recipient_bcc_maps.cf" ''
277 hosts = postgresql:///email
278 dbname = email
279 query = SELECT value FROM recipient_bcc_maps WHERE key = '%s'
280 ''}'';
259 }; 281 };
260 masterConfig = { 282 settings.master = {
261 "465" = { 283 "465" = {
262 type = "inet"; 284 type = "inet";
263 private = false; 285 private = false;
@@ -280,7 +302,7 @@ in {
280 hosts = postgresql:///email 302 hosts = postgresql:///email
281 dbname = email 303 dbname = email
282 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')) 304 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'))
283 ''},permit_tls_all_clientcerts,reject}'' 305 ''},check_policy_service unix:/run/postfix-internal-policy.sock,permit_tls_all_clientcerts,reject}''
284 "-o" "smtpd_relay_restrictions=permit_tls_all_clientcerts,reject" 306 "-o" "smtpd_relay_restrictions=permit_tls_all_clientcerts,reject"
285 "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}" 307 "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}"
286 "-o" "unverified_sender_reject_code=550" 308 "-o" "unverified_sender_reject_code=550"
@@ -310,7 +332,7 @@ in {
310 hosts = postgresql:///email 332 hosts = postgresql:///email
311 dbname = email 333 dbname = email
312 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')) 334 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'))
313 ''},permit_sasl_authenticated,reject}'' 335 ''},check_policy_service unix:/run/postfix-internal-policy.sock,permit_sasl_authenticated,reject}''
314 "-o" "smtpd_relay_restrictions=permit_sasl_authenticated,reject" 336 "-o" "smtpd_relay_restrictions=permit_sasl_authenticated,reject"
315 "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}" 337 "-o" "{smtpd_data_restrictions = check_policy_service unix:/run/postfwd3/postfwd3.sock}"
316 "-o" "unverified_sender_reject_code=550" 338 "-o" "unverified_sender_reject_code=550"
@@ -325,7 +347,10 @@ in {
325 maxproc = 0; 347 maxproc = 0;
326 args = [ 348 args = [
327 "-o" "header_checks=pcre:${pkgs.writeText "header_checks_submission" '' 349 "-o" "header_checks=pcre:${pkgs.writeText "header_checks_submission" ''
350 if /^Received: /
351 !/by surtr\.yggdrasil\.li/ STRIP
328 /^Received: from [^ ]+ \([^ ]+ [^ ]+\)\s+(.*)$/ REPLACE Received: $1 352 /^Received: from [^ ]+ \([^ ]+ [^ ]+\)\s+(.*)$/ REPLACE Received: $1
353 endif
329 ''}" 354 ''}"
330 ]; 355 ];
331 }; 356 };
@@ -373,7 +398,7 @@ in {
373 enable = true; 398 enable = true;
374 user = "postfix"; group = "postfix"; 399 user = "postfix"; group = "postfix";
375 socket = "local:/run/opendkim/opendkim.sock"; 400 socket = "local:/run/opendkim/opendkim.sock";
376 domains = ''csl:${concatStringsSep "," (["surtr.yggdrasil.li"] ++ emailDomains)}''; 401 domains = ''csl:${concatStringsSep "," (["surtr.yggdrasil.li" "yggdrasil.li" "141.li" "kleen.li" "synapse.li" "praseodym.org"] ++ emailDomains)}'';
377 selector = "surtr"; 402 selector = "surtr";
378 configFile = builtins.toFile "opendkim.conf" '' 403 configFile = builtins.toFile "opendkim.conf" ''
379 Syslog true 404 Syslog true
@@ -477,7 +502,7 @@ in {
477 }; 502 };
478 }; 503 };
479 504
480 users.groups.${config.services.rspamd.group}.members = [ config.services.postfix.user "dovecot2" ]; 505 users.groups.${config.services.rspamd.group}.members = [ config.services.postfix.user config.services.dovecot2.user ];
481 506
482 services.redis.servers.rspamd.enable = true; 507 services.redis.servers.rspamd.enable = true;
483 508
@@ -487,22 +512,22 @@ in {
487 services.dovecot2 = { 512 services.dovecot2 = {
488 enable = true; 513 enable = true;
489 enablePAM = false; 514 enablePAM = false;
490 sslServerCert = "/run/credentials/dovecot2.service/surtr.yggdrasil.li.pem"; 515 sslServerCert = "/run/credentials/dovecot.service/surtr.yggdrasil.li.pem";
491 sslServerKey = "/run/credentials/dovecot2.service/surtr.yggdrasil.li.key.pem"; 516 sslServerKey = "/run/credentials/dovecot.service/surtr.yggdrasil.li.key.pem";
492 sslCACert = toString ./ca/ca.crt; 517 sslCACert = toString ./ca/ca.crt;
493 mailLocation = "maildir:/var/lib/mail/%u/maildir:UTF-8:INDEX=/var/lib/dovecot/indices/%u"; 518 mailLocation = "maildir:/var/lib/mail/%u/maildir:UTF-8:INDEX=/var/lib/dovecot/indices/%u";
494 mailPlugins.globally.enable = [ "fts" "fts_xapian" ]; 519 mailPlugins.globally.enable = [ "fts" "fts_xapian" ];
495 protocols = [ "lmtp" "sieve" ]; 520 protocols = [ "lmtp" "sieve" ];
496 sieve = { 521 sieve = {
497 extensions = ["copy" "imapsieve" "variables" "imap4flags" "vacation"]; 522 extensions = ["copy" "imapsieve" "variables" "imap4flags" "vacation" "vacation-seconds" "vnd.dovecot.debug"];
498 globalExtensions = ["copy" "imapsieve" "variables" "imap4flags" "vacation"]; 523 globalExtensions = ["copy" "imapsieve" "variables" "imap4flags" "vacation" "vacation-seconds" "vnd.dovecot.debug"];
499 }; 524 };
500 extraConfig = let 525 extraConfig = let
501 dovecotSqlConf = pkgs.writeText "dovecot-sql.conf" '' 526 dovecotSqlConf = pkgs.writeText "dovecot-sql.conf" ''
502 driver = pgsql 527 driver = pgsql
503 connect = dbname=email 528 connect = dbname=email
504 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' 529 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'
505 user_query = SELECT "user", quota_rule, 'dovecot2' as uid, 'dovecot2' as gid FROM imap_user WHERE "user" = '%n' 530 user_query = SELECT "user", quota_rule, '${config.services.dovecot2.user}' as uid, 'dovecot2' as gid FROM imap_user WHERE "user" = '%n'
506 iterate_query = SELECT "user" FROM imap_user 531 iterate_query = SELECT "user" FROM imap_user
507 ''; 532 '';
508 in '' 533 in ''
@@ -510,16 +535,16 @@ in {
510 535
511 mail_plugins = $mail_plugins quota 536 mail_plugins = $mail_plugins quota
512 537
513 first_valid_uid = ${toString config.users.users.dovecot2.uid} 538 first_valid_uid = ${toString config.users.users.${config.services.dovecot2.user}.uid}
514 last_valid_uid = ${toString config.users.users.dovecot2.uid} 539 last_valid_uid = ${toString config.users.users.${config.services.dovecot2.user}.uid}
515 first_valid_gid = ${toString config.users.groups.dovecot2.gid} 540 first_valid_gid = ${toString config.users.groups.${config.services.dovecot2.group}.gid}
516 last_valid_gid = ${toString config.users.groups.dovecot2.gid} 541 last_valid_gid = ${toString config.users.groups.${config.services.dovecot2.group}.gid}
517 542
518 ${concatMapStringsSep "\n\n" (domain: 543 ${concatMapStringsSep "\n\n" (domain:
519 concatMapStringsSep "\n" (subdomain: '' 544 concatMapStringsSep "\n" (subdomain: ''
520 local_name ${subdomain} { 545 local_name ${subdomain} {
521 ssl_cert = </run/credentials/dovecot2.service/${subdomain}.pem 546 ssl_cert = </run/credentials/dovecot.service/${subdomain}.pem
522 ssl_key = </run/credentials/dovecot2.service/${subdomain}.key.pem 547 ssl_key = </run/credentials/dovecot.service/${subdomain}.key.pem
523 } 548 }
524 '') ["imap.${domain}" domain] 549 '') ["imap.${domain}" domain]
525 ) emailDomains} 550 ) emailDomains}
@@ -540,10 +565,10 @@ in {
540 auth_debug = yes 565 auth_debug = yes
541 566
542 service auth { 567 service auth {
543 user = dovecot2 568 user = ${config.services.dovecot2.user}
544 } 569 }
545 service auth-worker { 570 service auth-worker {
546 user = dovecot2 571 user = ${config.services.dovecot2.user}
547 } 572 }
548 573
549 userdb { 574 userdb {
@@ -564,7 +589,7 @@ in {
564 args = ${pkgs.writeText "dovecot-sql.conf" '' 589 args = ${pkgs.writeText "dovecot-sql.conf" ''
565 driver = pgsql 590 driver = pgsql
566 connect = dbname=email 591 connect = dbname=email
567 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 592 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
568 ''} 593 ''}
569 594
570 skip = never 595 skip = never
@@ -634,7 +659,7 @@ in {
634 quota_status_success = DUNNO 659 quota_status_success = DUNNO
635 quota_status_nouser = DUNNO 660 quota_status_nouser = DUNNO
636 quota_grace = 10%% 661 quota_grace = 10%%
637 quota_max_mail_size = ${config.services.postfix.config.message_size_limit} 662 quota_max_mail_size = ${toString config.services.postfix.settings.main.message_size_limit}
638 quota_vsizes = yes 663 quota_vsizes = yes
639 } 664 }
640 665
@@ -672,7 +697,7 @@ in {
672 plugin { 697 plugin {
673 plugin = fts fts_xapian 698 plugin = fts fts_xapian
674 fts = xapian 699 fts = xapian
675 fts_xapian = partial=2 full=20 attachments=1 verbose=1 700 fts_xapian = partial=3 full=20 attachments=1 verbose=1
676 701
677 fts_autoindex = yes 702 fts_autoindex = yes
678 703
@@ -687,12 +712,12 @@ in {
687 712
688 systemd.services.dovecot-fts-xapian-optimize = { 713 systemd.services.dovecot-fts-xapian-optimize = {
689 description = "Optimize dovecot indices for fts_xapian"; 714 description = "Optimize dovecot indices for fts_xapian";
690 requisite = [ "dovecot2.service" ]; 715 requisite = [ "dovecot.service" ];
691 after = [ "dovecot2.service" ]; 716 after = [ "dovecot.service" ];
692 startAt = "*-*-* 22:00:00 Europe/Berlin"; 717 startAt = "*-*-* 22:00:00 Europe/Berlin";
693 serviceConfig = { 718 serviceConfig = {
694 Type = "oneshot"; 719 Type = "oneshot";
695 ExecStart = "${pkgs.dovecot}/bin/doveadm fts optimize -A"; 720 ExecStart = "${getExe' pkgs.dovecot "doveadm"} fts optimize -A";
696 PrivateDevices = true; 721 PrivateDevices = true;
697 PrivateNetwork = true; 722 PrivateNetwork = true;
698 ProtectKernelTunables = true; 723 ProtectKernelTunables = true;
@@ -753,31 +778,29 @@ in {
753 778
754 security.acme.rfc2136Domains = { 779 security.acme.rfc2136Domains = {
755 "surtr.yggdrasil.li" = { 780 "surtr.yggdrasil.li" = {
756 restartUnits = [ "postfix.service" "dovecot2.service" ]; 781 restartUnits = [ "postfix.service" "dovecot.service" ];
757 }; 782 };
758 } // listToAttrs (map (domain: nameValuePair "spm.${domain}" { restartUnits = ["nginx.service"]; }) spmDomains) 783 } // listToAttrs (map (domain: nameValuePair "spm.${domain}" { restartUnits = ["nginx.service"]; }) spmDomains)
759 // listToAttrs (concatMap (domain: [ 784 // listToAttrs (concatMap (domain: [
760 (nameValuePair domain { restartUnits = ["postfix.service" "dovecot2.service"]; }) 785 (nameValuePair domain { restartUnits = ["postfix.service" "dovecot.service"]; })
761 (nameValuePair "mailin.${domain}" { restartUnits = ["postfix.service"]; }) 786 (nameValuePair "mailin.${domain}" { restartUnits = ["postfix.service"]; })
762 (nameValuePair "mailsub.${domain}" { restartUnits = ["postfix.service"]; }) 787 (nameValuePair "mailsub.${domain}" { restartUnits = ["postfix.service"]; })
763 (nameValuePair "imap.${domain}" { restartUnits = ["dovecot2.service"]; }) 788 (nameValuePair "imap.${domain}" { restartUnits = ["dovecot.service"]; })
764 (nameValuePair "mta-sts.${domain}" { restartUnits = ["nginx.service"]; }) 789 (nameValuePair "mta-sts.${domain}" { restartUnits = ["nginx.service"]; })
765 ]) emailDomains); 790 ]) emailDomains);
766 791
767 systemd.services.postfix = { 792 systemd.services.postfix = {
768 serviceConfig.LoadCredential = [ 793 serviceConfig.LoadCredential = let
769 "surtr.yggdrasil.li.key.pem:${config.security.acme.certs."surtr.yggdrasil.li".directory}/key.pem" 794 tlsCredential = domain: "${domain}.full.pem:${config.security.acme.certs.${domain}.directory}/full.pem";
770 "surtr.yggdrasil.li.pem:${config.security.acme.certs."surtr.yggdrasil.li".directory}/fullchain.pem" 795 in [
771 ] ++ concatMap (domain: 796 (tlsCredential "surtr.yggdrasil.li")
772 map (subdomain: "${subdomain}.full.pem:${config.security.acme.certs.${subdomain}.directory}/full.pem") 797 ] ++ concatMap (domain: map tlsCredential [domain "mailin.${domain}" "mailsub.${domain}"]) emailDomains;
773 [domain "mailin.${domain}" "mailsub.${domain}"]
774 ) emailDomains;
775 }; 798 };
776 799
777 systemd.services.dovecot2 = { 800 systemd.services.dovecot = {
778 preStart = '' 801 preStart = ''
779 for f in /etc/dovecot/sieve_flag.d/*.sieve /etc/dovecot/sieve_before.d/*.sieve; do 802 for f in /etc/dovecot/sieve_flag.d/*.sieve /etc/dovecot/sieve_before.d/*.sieve; do
780 ${pkgs.dovecot_pigeonhole}/bin/sievec $f 803 ${getExe' pkgs.dovecot_pigeonhole "sievec"} $f
781 done 804 done
782 ''; 805 '';
783 806
@@ -844,15 +867,16 @@ in {
844 charset utf-8; 867 charset utf-8;
845 source_charset utf-8; 868 source_charset utf-8;
846 ''; 869 '';
847 root = pkgs.runCommand "mta-sts.${domain}" {} '' 870 root = pkgs.writeTextFile {
848 mkdir -p $out/.well-known 871 name = "mta-sts.${domain}";
849 cp ${pkgs.writeText "mta-sts.${domain}.txt" '' 872 destination = "/.well-known/mta-sts.txt";
873 text = ''
850 version: STSv1 874 version: STSv1
851 mode: enforce 875 mode: enforce
852 max_age: 2419200 876 max_age: 2419200
853 mx: mailin.${domain} 877 mx: mailin.${domain}
854 ''} $out/.well-known/mta-sts.txt 878 '';
855 ''; 879 };
856 }; 880 };
857 }) emailDomains); 881 }) emailDomains);
858 }; 882 };
@@ -869,7 +893,7 @@ in {
869 systemd.services.spm = { 893 systemd.services.spm = {
870 serviceConfig = { 894 serviceConfig = {
871 Type = "notify"; 895 Type = "notify";
872 ExecStart = "${pkgs.spm}/bin/spm-server"; 896 ExecStart = getExe' pkgs.spm "spm-server";
873 User = "spm"; 897 User = "spm";
874 Group = "spm"; 898 Group = "spm";
875 899
@@ -927,7 +951,7 @@ in {
927 serviceConfig = { 951 serviceConfig = {
928 Type = "notify"; 952 Type = "notify";
929 953
930 ExecStart = "${ccert-policy-server}/bin/ccert-policy-server"; 954 ExecStart = getExe' ccert-policy-server "ccert-policy-server";
931 955
932 Environment = [ 956 Environment = [
933 "PGDATABASE=email" 957 "PGDATABASE=email"
@@ -960,6 +984,53 @@ in {
960 }; 984 };
961 users.groups."postfix-ccert-sender-policy" = {}; 985 users.groups."postfix-ccert-sender-policy" = {};
962 986
987 systemd.sockets."postfix-internal-policy" = {
988 requiredBy = ["postfix.service"];
989 wants = ["postfix-internal-policy.service"];
990 socketConfig = {
991 ListenStream = "/run/postfix-internal-policy.sock";
992 };
993 };
994 systemd.services."postfix-internal-policy" = {
995 after = [ "postgresql.service" ];
996 bindsTo = [ "postgresql.service" ];
997
998 serviceConfig = {
999 Type = "notify";
1000
1001 ExecStart = lib.getExe internal-policy-server;
1002
1003 Environment = [
1004 "PGDATABASE=email"
1005 ];
1006
1007 DynamicUser = false;
1008 User = "postfix-internal-policy";
1009 Group = "postfix-internal-policy";
1010 ProtectSystem = "strict";
1011 SystemCallFilter = "@system-service";
1012 NoNewPrivileges = true;
1013 ProtectKernelTunables = true;
1014 ProtectKernelModules = true;
1015 ProtectKernelLogs = true;
1016 ProtectControlGroups = true;
1017 MemoryDenyWriteExecute = true;
1018 RestrictSUIDSGID = true;
1019 KeyringMode = "private";
1020 ProtectClock = true;
1021 RestrictRealtime = true;
1022 PrivateDevices = true;
1023 PrivateTmp = true;
1024 ProtectHostname = true;
1025 ReadWritePaths = ["/run/postgresql"];
1026 };
1027 };
1028 users.users."postfix-internal-policy" = {
1029 isSystemUser = true;
1030 group = "postfix-internal-policy";
1031 };
1032 users.groups."postfix-internal-policy" = {};
1033
963 services.postfwd = { 1034 services.postfwd = {
964 enable = true; 1035 enable = true;
965 cache = false; 1036 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/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/postgresql/default.nix b/hosts/surtr/postgresql/default.nix
index 059f4088..3786ea7c 100644
--- a/hosts/surtr/postgresql/default.nix
+++ b/hosts/surtr/postgresql/default.nix
@@ -280,6 +280,64 @@ in {
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; 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;
281 281
282 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;
283 ''} 341 ''}
284 342
285 psql etebase postgres -eXf ${pkgs.writeText "etebase.sql" '' 343 psql etebase postgres -eXf ${pkgs.writeText "etebase.sql" ''
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/vidhar/audiobookshelf/abs-podcast-autoplaylist-gkleen.toml b/hosts/vidhar/audiobookshelf/abs-podcast-autoplaylist-gkleen.toml
index a5319e38..42920069 100644
--- a/hosts/vidhar/audiobookshelf/abs-podcast-autoplaylist-gkleen.toml
+++ b/hosts/vidhar/audiobookshelf/abs-podcast-autoplaylist-gkleen.toml
@@ -1,5 +1,5 @@
1{ 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]", 2 "data": "ENC[AES256_GCM,data:7STcG1J3zLHzlHi2LZgSa+pmKlYU6X1eLvjXcx7gvCuHFkXwa/ldrHdzaBXAMrQ+C+DWM4+cyhdal3ypxXueBuBEvH3P7/KofPP2A519sdZo1vDkXCzYRD3Ow74M+hX5ej6hL1mv21HayrRMPnYfH8HUWk1kd/y69EjpjvDHyCpQuw1WG5M4FDUaTd4Vh51QRCSG/Is29dEkvQowhIvNqnAR4KW9PpfjlbUPHauZ6PQLnlJmI9QCcAGJ8hHtp5T+xctTym82GAlXugTJpHnkqcxnGteu9H7UuiDMQ3aKKGYzZCpWkNHrbD1IRhzPzSjVlN2nIX3Ydp8ENNf61EGOrp5hzmV0MhwRHKfz5rE1FRVuyHcwhwBdJzfhzrL9zXZYzn9Zuf9KWwsF+1+58i9u9hPym63r4c1+RbMjNKzHc1/yhhsRDCrTwvMd/Hc6KfUi3H1wW/r02Va2bOCkYrOkWIQpBdx4ThMgkTaHr94DBMqT8n3Fzhc7+uaUsl6I9gMwTn8QCV3g4U7Mp3+/gnQLOdsCTgKr69x1iPfp7jzzbC29MWhIk+2aig+iWRMVT0n4AWIxooc3cYnfOJNtCdJwKuPUl4xNlZ22NK3XQfxBURSw0pi5KyDO1wgTVRoRJMnXZyin3bZ10OU8QKBqLp22FXpG/+mHrm3Y6yLkqfIP5ry+b86Rb7nWJlxDTwTGSoGi8sJzwNb+H9ioG7LjaSmvr7V+2aSq0+72WopAkIFSBq/yIWX3J/rNZuia6jKMY/8bSLDJz3V/YuzeUJ5feUkOS4KW1J1UkaYd6XxZ9Y8szCS/B7Ocz0YiV8fHmOzZKRn8BTmmXUIoyxO07MmwQ1shYfiVCwXjdjq2f3/CZbXNVOPvhGBYOcutUztftu3H9DRDTKrVae1TnztCVSkBSk1o52uSj0r8t6f4l1r7UG1TviOnVLVh1/6iiJVQey0m8G6ugw22zxfhI0Uea/rB70i+2UuoxrmsOfg+TgnvKBuakEBifp4rx0S5wyz05RYXkBLJTIwi2fVGmulqjZuCQNQnI3cfPI+oGcykob9IQqdo/Dwd118hNPVAwdDHyaiGXGh8eu8N2OCLQxlGZDtkVUveaEasJaZ0WWWaK97FUeoNJfUnB7Y3lNjv5u2rzviZyk4=,iv:jT21FNnHod6btDlBa3UflK3au5VmcsABs5OTMXF6oFA=,tag:Oh8cOL+edT5Wp0I1L5+vwg==,type:str]",
3 "sops": { 3 "sops": {
4 "age": [ 4 "age": [
5 { 5 {
@@ -11,8 +11,8 @@
11 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBiRWFqSHNlY1IvMkkwaEto\ncHZHa2p1Y25SakFkS2JYMlRFcFhnZGY1dVRFCkxSWmxvcHZMampQKzdKRHI0ZVMx\nUTFtR0pHbzFaQ0xQUFA2ZERDSWpwS0UKLS0tIFBaSGczY3VWdy9TKzRDZWZ2SElY\nbVQ4dDNhQllmVmViWGs5c3V4TmNscjQKeugevQJFAN/8JrzeAm4hm2JsQGb26BCb\n3dKYnN1kJU7oVHr1aVfXwMpELNYt9poX6WTY2h9lsdHuRlqoFXAA5Q==\n-----END AGE ENCRYPTED FILE-----\n" 11 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBiRWFqSHNlY1IvMkkwaEto\ncHZHa2p1Y25SakFkS2JYMlRFcFhnZGY1dVRFCkxSWmxvcHZMampQKzdKRHI0ZVMx\nUTFtR0pHbzFaQ0xQUFA2ZERDSWpwS0UKLS0tIFBaSGczY3VWdy9TKzRDZWZ2SElY\nbVQ4dDNhQllmVmViWGs5c3V4TmNscjQKeugevQJFAN/8JrzeAm4hm2JsQGb26BCb\n3dKYnN1kJU7oVHr1aVfXwMpELNYt9poX6WTY2h9lsdHuRlqoFXAA5Q==\n-----END AGE ENCRYPTED FILE-----\n"
12 } 12 }
13 ], 13 ],
14 "lastmodified": "2025-05-10T10:25:15Z", 14 "lastmodified": "2025-08-11T07:08:36Z",
15 "mac": "ENC[AES256_GCM,data:dhj7e+vF3uiR6I22PR5tdNdM8EyrWmGGTIqjj8H7IdNIsZBHzjeHlBDFOwN7z/JMO0BVwIi4DmhApg2BSPGsQZGDQZ28UTCC8TDtd1zmfGtSP8R8AFHADYdLK/desMtHg6BZTnLv5tpba34WWdflMNOQpwgWPZsIk/DkLaoXdvk=,iv:qkoAZngTz2sfWdxDs+h8Mb2IrkF8gqnQoR5iRoeKjbY=,tag:zXrkBJmPM4ItJxMnX8IDxQ==,type:str]", 15 "mac": "ENC[AES256_GCM,data:ZL/dOz+NC8sr8vPBsux+gFOWxUhQqMSmG1az7udhB0ckmOXtnrPBzMM1gs+5pwXLvfLux0m4xzT87+o87axIECnCq35FSuMjtEBK24OUJXsLG/q/tDv5dfRBy/976dM5W7YkBVX/uc03p8CLKf5w4XYNeRKnSwjLvWGd9runDOU=,iv:9ZIeJ5aDVVPHi3/oHqWkWtEfeivV/nFFyQ1lJWJwMu8=,tag:TfkHaopMa+Z0zk38A6/NTA==,type:str]",
16 "unencrypted_suffix": "_unencrypted", 16 "unencrypted_suffix": "_unencrypted",
17 "version": "3.10.2" 17 "version": "3.10.2"
18 } 18 }
diff --git a/hosts/vidhar/default.nix b/hosts/vidhar/default.nix
index c9470ee9..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 ./paperless ./hledger ./audiobookshelf 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/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/ruleset.nft b/hosts/vidhar/network/ruleset.nft
index 6b0ac9fc..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
@@ -95,6 +96,7 @@ table inet filter {
95 counter paperless-rx {} 96 counter paperless-rx {}
96 counter hledger-rx {} 97 counter hledger-rx {}
97 counter audiobookshelf-rx {} 98 counter audiobookshelf-rx {}
99 counter kimai-rx {}
98 100
99 counter established-rx {} 101 counter established-rx {}
100 102
@@ -127,6 +129,7 @@ table inet filter {
127 counter paperless-tx {} 129 counter paperless-tx {}
128 counter hledger-tx {} 130 counter hledger-tx {}
129 counter audiobookshelf-tx {} 131 counter audiobookshelf-tx {}
132 counter kimai-tx {}
130 133
131 counter tx {} 134 counter tx {}
132 135
@@ -150,8 +153,13 @@ table inet filter {
150 153
151 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
152 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
153 157
154 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
155 163
156 164
157 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
@@ -266,7 +274,7 @@ table inet filter {
266 274
267table inet nat { 275table inet nat {
268 counter gpon-nat {} 276 counter gpon-nat {}
269 # counter container-nat {} 277 counter kimai-nat {}
270 278
271 chain postrouting { 279 chain postrouting {
272 type nat hook postrouting priority srcnat 280 type nat hook postrouting priority srcnat
@@ -274,7 +282,7 @@ table inet nat {
274 282
275 283
276 meta nfproto ipv4 oifname gpon counter name gpon-nat masquerade 284 meta nfproto ipv4 oifname gpon counter name gpon-nat masquerade
277 # iifname ve-* oifname gpon counter name container-nat masquerade 285 iifname ve-kimai oifname gpon counter name kimai-nat masquerade
278 } 286 }
279} 287}
280 288
diff --git a/hosts/vidhar/prometheus/default.nix b/hosts/vidhar/prometheus/default.nix
index b1d90d47..094f9f7a 100644
--- a/hosts/vidhar/prometheus/default.nix
+++ b/hosts/vidhar/prometheus/default.nix
@@ -27,6 +27,7 @@ in {
27 27
28 extraFlags = [ 28 extraFlags = [
29 "--web.enable-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/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
index 2532cfc3..f526a434 100644
--- a/modules/abs-podcast-autoplaylist.nix
+++ b/modules/abs-podcast-autoplaylist.nix
@@ -37,6 +37,7 @@ in {
37 PrivateDevices = true; 37 PrivateDevices = true;
38 Type = "oneshot"; 38 Type = "oneshot";
39 ExecStart = "${lib.getExe pkgs.abs-podcast-autoplaylist} %I.toml"; 39 ExecStart = "${lib.getExe pkgs.abs-podcast-autoplaylist} %I.toml";
40 TimeoutSec = "5min";
40 }; 41 };
41 }; 42 };
42 } // lib.mapAttrs' (name: { configSecret, ... }: lib.nameValuePair "abs-podcast-autoplaylist@${utils.escapeSystemdPath name}" { 43 } // lib.mapAttrs' (name: { configSecret, ... }: lib.nameValuePair "abs-podcast-autoplaylist@${utils.escapeSystemdPath name}" {
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 8e1afc27..af021777 100644
--- a/modules/borgcopy/default.nix
+++ b/modules/borgcopy/default.nix
@@ -1,25 +1,32 @@
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}" {
25 restartIfChanged = false; 32 restartIfChanged = false;
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/pgbackrest.nix b/modules/pgbackrest.nix
index 81c74a8e..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";
diff --git a/modules/postsrsd.nix b/modules/postsrsd.nix
index 205e669d..bc941e3e 100644
--- a/modules/postsrsd.nix
+++ b/modules/postsrsd.nix
@@ -101,6 +101,12 @@ in
101 type = lib.types.lines; 101 type = lib.types.lines;
102 default = ""; 102 default = "";
103 }; 103 };
104
105 configurePostfix = lib.mkOption {
106 type = lib.types.bool;
107 default = false;
108 description = "noop";
109 };
104 }; 110 };
105 }; 111 };
106 112
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/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/overlays/abs-podcast-autoplaylist/default.nix b/overlays/abs-podcast-autoplaylist/default.nix
index 075e0ba0..843f1b65 100644
--- a/overlays/abs-podcast-autoplaylist/default.nix
+++ b/overlays/abs-podcast-autoplaylist/default.nix
@@ -1,23 +1,14 @@
1{ prev, final, flakeInputs, ... }: 1{ prev, final, flake, flakeInputs, ... }:
2
3with flakeInputs;
4 2
5let 3let
6 workspace = uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./.; }; 4 workspace = flakeInputs.uv2nix.lib.workspace.loadWorkspace { workspaceRoot = ./.; };
7 overlay = workspace.mkPyprojectOverlay { 5 pythonSet = flake.lib.pythonSet {
8 sourcePreference = "wheel"; 6 pkgs = final;
7 python = final.python312;
8 overlay = workspace.mkPyprojectOverlay {
9 sourcePreference = "wheel";
10 };
9 }; 11 };
10 python = final.python312;
11 pythonSet =
12 (final.callPackage pyproject-nix.build.packages {
13 inherit python;
14 }).overrideScope
15 (
16 prev.lib.composeManyExtensions [
17 pyproject-build-systems.overlays.default
18 overlay
19 ]
20 );
21 virtualEnv = pythonSet.mkVirtualEnv "abs-podcast-autoplaylist-env" workspace.deps.default; 12 virtualEnv = pythonSet.mkVirtualEnv "abs-podcast-autoplaylist-env" workspace.deps.default;
22in { 13in {
23 abs-podcast-autoplaylist = virtualEnv.overrideAttrs (oldAttrs: { 14 abs-podcast-autoplaylist = virtualEnv.overrideAttrs (oldAttrs: {
diff --git a/overlays/deploy-rs.nix b/overlays/deploy-rs.nix
index 0bf1c3b2..678c6f5f 100644
--- a/overlays/deploy-rs.nix
+++ b/overlays/deploy-rs.nix
@@ -2,13 +2,15 @@
2 flakeInputs.deploy-rs.overlays.default 2 flakeInputs.deploy-rs.overlays.default
3 (final: prev: { 3 (final: prev: {
4 deploy-rs = prev.deploy-rs // { 4 deploy-rs = prev.deploy-rs // {
5 deploy-rs = prev.deploy-rs.deploy-rs.overrideAttrs (oldAttrs: { 5 deploy-rs = prev.symlinkJoin {
6 nativeBuildInputs = (oldAttrs.nativeBuildInputs or []) ++ [final.makeWrapper]; 6 name = "${prev.deploy-rs.deploy-rs.name}-wrapped";
7 preFixup = '' 7 paths = [ prev.deploy-rs.deploy-rs ];
8 buildInputs = [ prev.makeWrapper ];
9 postBuild = ''
8 wrapProgram $out/bin/deploy \ 10 wrapProgram $out/bin/deploy \
9 --prefix PATH : ${prev.lib.makeBinPath (with final; [ nix-monitored ])} 11 --prefix PATH : ${prev.lib.makeBinPath (with final; [ nix-monitored ])}
10 ''; 12 '';
11 }); 13 };
12 }; 14 };
13 }) 15 })
14 final prev 16 final prev
diff --git a/overlays/etesync-dav.nix b/overlays/etesync-dav.nix
index cec216e2..e0ced1e3 100644
--- a/overlays/etesync-dav.nix
+++ b/overlays/etesync-dav.nix
@@ -1,55 +1,58 @@
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.1";
5 pyproject = true;
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-y4BhU2kSn+RWqc5+pJQFhbwfat9cMWD0ED0EXJp25cY=";
11 }; 12 };
12 13
13 dependencies = with prev.python3Packages; [ 14 build-system = with final.python3Packages; [ setuptools ];
15
16 dependencies = with final.python3Packages; [
14 appdirs 17 appdirs
15 etebase 18 etebase
16 etesync 19 etesync
17 flask 20 flask
18 flask-wtf 21 flask-wtf
19 msgpack 22 msgpack
20 setuptools 23 requests
21 (toPythonModule (buildPythonApplication rec { 24 requests.optional-dependencies.socks
25 (buildPythonApplication rec {
22 pname = "radicale"; 26 pname = "radicale";
23 version = "3.2.3"; 27 version = "3.2.0";
24 pyproject = true; 28 pyproject = true;
25 29
26 src = prev.fetchFromGitHub { 30 src = prev.fetchFromGitHub {
27 owner = "Kozea"; 31 owner = "Kozea";
28 repo = "Radicale"; 32 repo = "Radicale";
29 rev = "refs/tags/v${version}"; 33 rev = "refs/tags/v${version}";
30 hash = "sha256-1IlnXVetQQuKBt6+QVKNeMM6qBQAiUhqc+4x3xOnSdE="; 34 hash = "sha256-RxC8VOfdTXJZiAroDHTKjJqGWu65Z5uyb4WK1LOqubQ=";
31 }; 35 };
32 36
37 postPatch = ''
38 sed -i '/addopts/d' setup.cfg
39 '';
40
33 build-system = [ 41 build-system = [
34 setuptools 42 setuptools
35 ]; 43 ];
36 44
37 dependencies = 45 dependencies = [
38 [ 46 defusedxml
39 defusedxml 47 passlib
40 passlib 48 vobject
41 vobject 49 pika
42 pika 50 python-dateutil
43 python-dateutil 51 pytz # https://github.com/Kozea/Radicale/issues/816
44 pytz # https://github.com/Kozea/Radicale/issues/816 52 ] ++ passlib.optional-dependencies.bcrypt;
45 ]
46 ++ passlib.optional-dependencies.bcrypt;
47 53
48 doCheck = false; 54 doCheck = false;
49 })) 55 })
50 requests
51 types-setuptools
52 requests.optional-dependencies.socks
53 ]; 56 ];
54 57
55 doCheck = false; 58 doCheck = false;
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/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
index b4601a03..5e715dae 100644
--- a/overlays/swayosd/default.nix
+++ b/overlays/swayosd/default.nix
@@ -4,7 +4,7 @@
4 cargoDeps = prev.rustPlatform.fetchCargoVendor { 4 cargoDeps = prev.rustPlatform.fetchCargoVendor {
5 inherit (oldAttrs) pname; 5 inherit (oldAttrs) pname;
6 inherit version src; 6 inherit version src;
7 hash = "sha256-yWybf4GKxHrk4WrW5SmjfPD0Gv79tpXOwNLlWeykYy0="; 7 hash = "sha256-J2sl6/4+bRWlkvaTJtFsMqvvOxYtWLRjJcYWcu0loRE=";
8 }; 8 };
9 patches = (oldAttrs.patches or []) ++ [ 9 patches = (oldAttrs.patches or []) ++ [
10 ./exponential.patch 10 ./exponential.patch
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/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 1d8433af..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.python312; 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 7c1ca91d..00000000
--- a/overlays/worktime/poetry.lock
+++ /dev/null
@@ -1,284 +0,0 @@
1# This file is automatically @generated by Poetry 2.1.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"
9groups = ["main"]
10files = [
11 {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"},
12 {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"},
13]
14
15[[package]]
16name = "certifi"
17version = "2025.1.31"
18description = "Python package for providing Mozilla's CA Bundle."
19optional = false
20python-versions = ">=3.6"
21groups = ["main"]
22files = [
23 {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"},
24 {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"},
25]
26
27[[package]]
28name = "charset-normalizer"
29version = "3.4.1"
30description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
31optional = false
32python-versions = ">=3.7"
33groups = ["main"]
34files = [
35 {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"},
36 {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"},
37 {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"},
38 {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"},
39 {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"},
40 {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"},
41 {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"},
42 {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"},
43 {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"},
44 {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"},
45 {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"},
46 {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"},
47 {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"},
48 {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"},
49 {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"},
50 {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"},
51 {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"},
52 {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"},
53 {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"},
54 {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"},
55 {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"},
56 {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"},
57 {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"},
58 {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"},
59 {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"},
60 {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"},
61 {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"},
62 {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"},
63 {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"},
64 {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"},
65 {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"},
66 {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"},
67 {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"},
68 {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"},
69 {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"},
70 {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"},
71 {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"},
72 {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"},
73 {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"},
74 {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"},
75 {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"},
76 {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"},
77 {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"},
78 {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"},
79 {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"},
80 {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"},
81 {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"},
82 {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"},
83 {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"},
84 {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"},
85 {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"},
86 {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"},
87 {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"},
88 {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"},
89 {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"},
90 {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"},
91 {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"},
92 {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"},
93 {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"},
94 {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"},
95 {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"},
96 {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"},
97 {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"},
98 {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"},
99 {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"},
100 {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"},
101 {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"},
102 {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"},
103 {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"},
104 {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"},
105 {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"},
106 {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"},
107 {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"},
108 {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"},
109 {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"},
110 {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"},
111 {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"},
112 {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"},
113 {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"},
114 {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"},
115 {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"},
116 {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"},
117 {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"},
118 {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"},
119 {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"},
120 {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"},
121 {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"},
122 {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"},
123 {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"},
124 {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"},
125 {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"},
126 {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"},
127]
128
129[[package]]
130name = "idna"
131version = "3.10"
132description = "Internationalized Domain Names in Applications (IDNA)"
133optional = false
134python-versions = ">=3.6"
135groups = ["main"]
136files = [
137 {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
138 {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
139]
140
141[package.extras]
142all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
143
144[[package]]
145name = "jsonpickle"
146version = "4.0.5"
147description = "jsonpickle encodes/decodes any Python object to/from JSON"
148optional = false
149python-versions = ">=3.8"
150groups = ["main"]
151files = [
152 {file = "jsonpickle-4.0.5-py3-none-any.whl", hash = "sha256:b4ac7d0a75ddcdfd93445737f1d36ff28768690d43e54bf5d0ddb1d915e580df"},
153 {file = "jsonpickle-4.0.5.tar.gz", hash = "sha256:f299818b39367c361b3f26bdba827d4249ab5d383cd93144d0f94b5417aacb35"},
154]
155
156[package.extras]
157cov = ["pytest-cov"]
158dev = ["black", "pyupgrade"]
159docs = ["furo", "rst.linker (>=1.9)", "sphinx (>=3.5)"]
160packaging = ["build", "setuptools (>=61.2)", "setuptools-scm[toml] (>=6.0)", "twine"]
161testing = ["PyYAML", "atheris (>=2.3.0,<2.4.0) ; python_version < \"3.12\"", "bson", "ecdsa", "feedparser", "gmpy2", "numpy", "pandas", "pymongo", "pytest (>=6.0,!=8.1.*)", "pytest-benchmark", "pytest-benchmark[histogram]", "pytest-checkdocs (>=1.2.3)", "pytest-enabler (>=1.0.1)", "pytest-ruff (>=0.2.1)", "scikit-learn", "scipy (>=1.9.3) ; python_version > \"3.10\"", "scipy ; python_version <= \"3.10\"", "simplejson", "sqlalchemy", "ujson"]
162
163[[package]]
164name = "python-dateutil"
165version = "2.9.0.post0"
166description = "Extensions to the standard Python datetime module"
167optional = false
168python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
169groups = ["main"]
170files = [
171 {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
172 {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
173]
174
175[package.dependencies]
176six = ">=1.5"
177
178[[package]]
179name = "pyxdg"
180version = "0.28"
181description = "PyXDG contains implementations of freedesktop.org standards in python."
182optional = false
183python-versions = "*"
184groups = ["main"]
185files = [
186 {file = "pyxdg-0.28-py2.py3-none-any.whl", hash = "sha256:bdaf595999a0178ecea4052b7f4195569c1ff4d344567bccdc12dfdf02d545ab"},
187 {file = "pyxdg-0.28.tar.gz", hash = "sha256:3267bb3074e934df202af2ee0868575484108581e6f3cb006af1da35395e88b4"},
188]
189
190[[package]]
191name = "requests"
192version = "2.32.3"
193description = "Python HTTP for Humans."
194optional = false
195python-versions = ">=3.8"
196groups = ["main"]
197files = [
198 {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
199 {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
200]
201
202[package.dependencies]
203certifi = ">=2017.4.17"
204charset-normalizer = ">=2,<4"
205idna = ">=2.5,<4"
206urllib3 = ">=1.21.1,<3"
207
208[package.extras]
209socks = ["PySocks (>=1.5.6,!=1.5.7)"]
210use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
211
212[[package]]
213name = "six"
214version = "1.17.0"
215description = "Python 2 and 3 compatibility utilities"
216optional = false
217python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
218groups = ["main"]
219files = [
220 {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
221 {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
222]
223
224[[package]]
225name = "tabulate"
226version = "0.9.0"
227description = "Pretty-print tabular data"
228optional = false
229python-versions = ">=3.7"
230groups = ["main"]
231files = [
232 {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"},
233 {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"},
234]
235
236[package.extras]
237widechars = ["wcwidth"]
238
239[[package]]
240name = "toml"
241version = "0.10.2"
242description = "Python Library for Tom's Obvious, Minimal Language"
243optional = false
244python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
245groups = ["main"]
246files = [
247 {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
248 {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
249]
250
251[[package]]
252name = "uritools"
253version = "4.0.3"
254description = "URI parsing, classification and composition"
255optional = false
256python-versions = ">=3.7"
257groups = ["main"]
258files = [
259 {file = "uritools-4.0.3-py3-none-any.whl", hash = "sha256:bae297d090e69a0451130ffba6f2f1c9477244aa0a5543d66aed2d9f77d0dd9c"},
260 {file = "uritools-4.0.3.tar.gz", hash = "sha256:ee06a182a9c849464ce9d5fa917539aacc8edd2a4924d1b7aabeeecabcae3bc2"},
261]
262
263[[package]]
264name = "urllib3"
265version = "2.3.0"
266description = "HTTP library with thread-safe connection pooling, file post, and more."
267optional = false
268python-versions = ">=3.9"
269groups = ["main"]
270files = [
271 {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"},
272 {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"},
273]
274
275[package.extras]
276brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""]
277h2 = ["h2 (>=4,<5)"]
278socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
279zstd = ["zstandard (>=0.18.0)"]
280
281[metadata]
282lock-version = "2.1"
283python-versions = "^3.12"
284content-hash = "2b335da94bf3e2d2bee7d8ca6e84cdb56e97ac29d1224d8c8dca98d93bbdcea2"
diff --git a/overlays/worktime/pyproject.toml b/overlays/worktime/pyproject.toml
index de4b9fd4..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.12"
9pyxdg = "^0.28"
10python-dateutil = "^2.9.0.post0"
11uritools = "^4.0.3"
12requests = "^2.32.3"
13tabulate = "^0.9.0"
14backoff = "^2.2.1"
15toml = "^0.10.2"
16jsonpickle = "^4.0.5"
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 4eee5dc2..3e7aeb9f 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
@@ -27,77 +29,76 @@ from 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
41import json 43import json
42 44
43class TogglAPISection(Enum): 45import asyncio
44 TOGGL = '/api/v9' 46
45 REPORTS = '/reports/api/v2' 47from frozendict import frozendict
46 48from contextlib import closing
47class TogglAPIError(Exception): 49import os
48 def __init__(self, response, *, http_error=None): 50from time import clock_gettime_ns, CLOCK_MONOTONIC
49 self.http_error = http_error 51from atomicwriter import AtomicWriter
50 self.response = response 52import desktop_notify.aio as notify
51 53
52 def __str__(self): 54class BearerAuth(requests.auth.AuthBase):
53 if not self.http_error is None: 55 def __init__(self, token):
54 return str(self.http_error) 56 self.token = token
55 else: 57 def __call__(self, r):
56 return self.response.text 58 r.headers["authorization"] = "Bearer " + self.token
57 59 return r
58class TogglAPI(object): 60
59 def __init__(self, api_token, workspace_id, client_ids): 61class KimaiSession(requests.Session):
60 self._api_token = api_token 62 def __init__(self, base_url: str, api_token: str):
61 self._workspace_id = workspace_id 63 super().__init__()
62 self._client_ids = set(map(int, client_ids.split(','))) if client_ids else None 64 self.base_url = base_url
63 65 self.auth = BearerAuth(api_token)
64 def _make_url(self, api=TogglAPISection.TOGGL, section=['me', 'time_entries', 'current'], params={}): 66 retries = Retry(total=5, backoff_factor=0.1, status_forcelist=[500, 502, 503, 504])
65 if api is TogglAPISection.REPORTS: 67 super().mount(base_url, HTTPAdapter(max_retries=retries))
66 params.update({'user_agent': 'worktime', 'workspace_id': self._workspace_id}) 68
67 69 def request(self, method, url, *args, **kwargs):
68 api_path = api.value 70 joined_url = urljoin(self.base_url, url)
69 section_path = '/'.join(section) 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']})
70 uri = uricompose(scheme='https', host='api.track.toggl.com', path=f"{api_path}/{section_path}", query=params) 72
71 73class KimaiAPI(object):
72 return uri 74 def __init__(self, base_url: str, api_token: str, clients: Iterable[str]):
73 75 self._session = KimaiSession(base_url, api_token)
74 def _query(self, url, method): 76 self._kimai_clients = self._session.get('/api/customers').json()
75 response = self._raw_query(url, method) 77 self._client_ids = self.resolve_clients(clients)
76 response.raise_for_status() 78 kimai_user = self._session.get('/api/users/me').json()
77 return response 79 self._tz = gettz(kimai_user['timezone'])
78 80
79 @backoff.on_predicate( 81 def resolve_clients(self, clients: Iterable[str]) -> frozenset[int]:
80 backoff.expo, 82 return frozenset({ client['id'] for client in self._kimai_clients if client['name'] in clients })
81 factor=0.1, max_value=2, 83
82 predicate=lambda r: r.status_code == 429, 84 def render_datetime(self, datetime: datetime) -> str:
83 max_time=10, 85 return datetime.astimezone(self._tz).strftime('%Y-%m-%dT%H:%M:%S')
84 ) 86
85 def _raw_query(self, url, method): 87 def get_timesheets(self, params: dict[str, Any] = {}) -> Generator[Any]:
86 headers = {'content-type': 'application/json', 'accept': 'application/json'} 88 for page in count(start=1):
87 response = None 89 resp = self._session.get('/api/timesheets', params=params | {'size': 100, 'page': page})
88 90 if resp.status_code == 404:
89 if method == 'GET': 91 break
90 response = requests.get(url, headers=headers, auth=HTTPBasicAuth(self._api_token, 'api_token')) 92 yield from resp.json()
91 elif method == 'POST':
92 response = requests.post(url, headers=headers, auth=HTTPBasicAuth(self._api_token, 'api_token'))
93 else:
94 raise ValueError(f"Undefined HTTP method “{method}”")
95
96 return response
97 93
98 def entry_durations(self, start_date, *, end_date, rounding=False, client_ids): 94 def entry_durations(self, start_date: datetime, *, end_date: datetime, clients: Iterable[str] | None = None) -> Generator[timedelta]:
99 if client_ids is not None and not client_ids: 95 client_ids = None
96 if clients is not None and not clients:
100 return 97 return
98 elif clients is None:
99 client_ids = self._client_ids
100 else:
101 client_ids = self.resolve_clients(clients)
101 102
102 cache_dir = Path(BaseDirectory.save_cache_path('worktime')) / 'entry_durations' 103 cache_dir = Path(BaseDirectory.save_cache_path('worktime')) / 'entry_durations'
103 step = timedelta(days = 120) 104 step = timedelta(days = 120)
@@ -116,11 +117,8 @@ class TogglAPI(object):
116 cache_key = blake2s(jsonpickle.encode({ 117 cache_key = blake2s(jsonpickle.encode({
117 'start': req_start, 118 'start': req_start,
118 'end': req_end, 119 'end': req_end,
119 'rounding': rounding, 120 'client_ids': client_ids,
120 'clients': client_ids, 121 }).encode('utf-8'), key = self._session.auth.token.encode('utf-8')).hexdigest()
121 'workspace': self._workspace_id,
122 'workspace_clients': self._client_ids
123 }).encode('utf-8'), key = self._api_token.encode('utf-8')).hexdigest()
124 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'
125 try: 123 try:
126 with cache_path.open('r', encoding='utf-8') as ch: 124 with cache_path.open('r', encoding='utf-8') as ch:
@@ -130,85 +128,83 @@ class TogglAPI(object):
130 pass 128 pass
131 129
132 entries = list() 130 entries = list()
133 params = { 'since': (req_start - timedelta(days=1)).date().isoformat(), 131 params = {
134 'until': (req_end + timedelta(days=1)).date().isoformat(), 132 'begin': self.render_datetime(req_start),
135 'rounding': 'yes' if rounding else 'no', 133 'end': self.render_datetime(req_end),
136 'billable': 'yes' 134 'customers[]': list(client_ids),
137 } 135 'billable': 1,
138 if client_ids is not None: 136 }
139 params |= { 'client_ids': ','.join(map(str, client_ids)) } 137
140 for page in count(start = 1): 138 for entry in self.get_timesheets(params):
141 url = self._make_url(api = TogglAPISection.REPORTS, section = ['details'], params = params | { 'page': page }) 139 if entry['end'] is None:
142 r = self._query(url = url, method='GET') 140 continue
143 if not r or not r.json(): 141
144 raise TogglAPIError(r) 142 start = isoparse(entry['begin'])
145 report = r.json() 143 end = isoparse(entry['end'])
146 for entry in report['data']:
147 start = isoparse(entry['start'])
148 end = isoparse(entry['end'])
149
150 if start > req_end or end < req_start:
151 continue
152 144
153 x = min(end, req_end) - max(start, req_start) 145 if start > req_end or end < req_start:
154 if cache_key: 146 continue
155 entries.append(x) 147
156 yield x 148 x = min(end, req_end) - max(start, req_start)
157 if not report['data']: 149 if cache_key:
158 break 150 entries.append(x)
151 yield x
159 152
160 if cache_path: 153 if cache_path:
161 cache_path.parent.mkdir(parents=True, exist_ok=True) 154 cache_path.parent.mkdir(parents=True, exist_ok=True)
162 with cache_path.open('w', encoding='utf-8') as ch: 155 with cache_path.open('w', encoding='utf-8') as ch:
163 ch.write(jsonpickle.encode(entries)) 156 ch.write(jsonpickle.encode(entries))
164 # res = timedelta(milliseconds=report['total_billable']) if report['total_billable'] else timedelta(milliseconds=0)
165 # return res
166
167 def get_billable_hours(self, start_date, end_date=datetime.now(timezone.utc), rounding=False):
168 billable_acc = timedelta(milliseconds = 0)
169 if 0 in self._client_ids:
170 url = self._make_url(api = TogglAPISection.TOGGL, section = ['workspaces', self._workspace_id, 'clients'])
171 r = self._query(url = url, method = 'GET')
172 if not r or not r.json():
173 raise TogglAPIError(r)
174 157
175 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)) 158 def get_billable_hours(self, start_date: datetime, end_date: datetime = datetime.now(timezone.utc)) -> timedelta:
159 return sum(self.entry_durations(start_date, end_date=end_date), start=timedelta(milliseconds=0))
176 160
177 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)) 161 def get_running_entry(self) -> Any | None:
178 162 kimai_entries = self._session.get('/api/timesheets/active').json()
179 return billable_acc 163 if not kimai_entries:
164 return None
165 entry = kimai_entries[0]
180 166
181 def get_running_clock(self, now=datetime.now(timezone.utc)): 167 if entry['project']['customer']['id'] not in self._client_ids:
182 url = self._make_url(api = TogglAPISection.TOGGL, section = ['me', 'time_entries', 'current']) 168 return None
183 r = self._query(url = url, method='GET')
184 169
185 if not r or (not r.json() and r.json() is not None): 170 return entry
186 raise TogglAPIError(r)
187 171
188 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:
189 return None 175 return None
176 start = isoparse(entry['begin'])
177 return now - start if start <= now else None
190 178
191 if self._client_ids is not None: 179 def get_recent_entries(self) -> Generator[Any]:
192 if 'pid' in r.json() and r.json()['pid']: 180 step = timedelta(days = 7)
193 url = self._make_url(api = TogglAPISection.TOGGL, section = ['projects', str(r.json()['pid'])]) 181 now = datetime.now().astimezone(timezone.utc)
194 pr = self._query(url = url, method = 'GET') 182 ids = set()
195 if not pr or not pr.json(): 183 for req_end in (now - step * i for i in count()):
196 raise TogglAPIError(pr) 184 params = {
197 185 'begin': self.render_datetime(req_end - step),
198 if not pr.json(): 186 'end': self.render_datetime(req_end),
199 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
200 194
201 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):
202 if pr.json()['cid'] not in self._client_ids: 196 self._session.post('/api/timesheets', json={
203 return None 197 'begin': self.render_datetime(datetime.now()),
204 elif 0 not in self._client_ids: 198 'project': project_id,
205 return None 199 'activity': activity_id,
206 elif 0 not in self._client_ids: 200 'description': description if description else '',
207 return None 201 'tags': (','.join(tags)) if tags else '',
202 'billable': billable,
203 }).raise_for_status()
208 204
209 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()
210 207
211 return now - start if start <= now else None
212 208
213class Worktime(object): 209class Worktime(object):
214 time_worked = timedelta() 210 time_worked = timedelta()
@@ -281,10 +277,10 @@ class Worktime(object):
281 277
282 config = Worktime.config() 278 config = Worktime.config()
283 config_dir = BaseDirectory.load_first_config('worktime') 279 config_dir = BaseDirectory.load_first_config('worktime')
284 api = TogglAPI( 280 api = KimaiAPI(
285 api_token=config.get("TOGGL", {}).get("ApiToken", None), 281 base_url=config.get("KIMAI", {}).get("BaseUrl", None),
286 workspace_id=config.get("TOGGL", {}).get("Workspace", None), 282 api_token=config.get("KIMAI", {}).get("ApiToken", None),
287 client_ids=config.get("TOGGL", {}).get("ClientIds", None) 283 clients=config.get("KIMAI", {}).get("Clients", None)
288 ) 284 )
289 date_format = config.get("WORKTIME", {}).get("DateFormat", '%Y-%m-%d') 285 date_format = config.get("WORKTIME", {}).get("DateFormat", '%Y-%m-%d')
290 286
@@ -379,8 +375,8 @@ class Worktime(object):
379 parse_datestr(stripped_line) 375 parse_datestr(stripped_line)
380 376
381 for day in [fromDay + timedelta(days = x) for x in range(0, (toDay - fromDay).days + 1)]: 377 for day in [fromDay + timedelta(days = x) for x in range(0, (toDay - fromDay).days + 1)]:
382 if self.end_date.date() < day or day < self.start_date.date(): 378 # if self.end_date.date() < day or day < self.start_date.date():
383 continue 379 # continue
384 380
385 if self.would_be_workday(day): 381 if self.would_be_workday(day):
386 if excused_kind == 'leave': 382 if excused_kind == 'leave':
@@ -397,9 +393,29 @@ class Worktime(object):
397 start_day = self.start_date.date() 393 start_day = self.start_date.date()
398 end_day = self.end_date.date() 394 end_day = self.end_date.date()
399 395
396 self.extra_days_to_work = dict()
397
400 try: 398 try:
401 with open(Path(config_dir) / "pull-forward", 'r') as excused: 399 with open(Path(config_dir) / "days-to-work", 'r') as extra_days_to_work_file:
402 for line in excused: 400 for line in extra_days_to_work_file:
401 stripped_line = line.strip()
402 if stripped_line:
403 splitLine = stripped_line.split(' ')
404 if len(splitLine) == 2:
405 [hours, datestr] = splitLine
406 day = datetime.strptime(datestr, date_format).replace(tzinfo=tzlocal()).date()
407 self.extra_days_to_work[day] = timedelta(hours = float(hours))
408 else:
409 day = datetime.strptime(stripped_line, date_format).replace(tzinfo=tzlocal()).date()
410 self.extra_days_to_work[day] = self.time_per_day(day)
411 except IOError as e:
412 if e.errno != 2:
413 raise e
414
415
416 try:
417 with open(Path(config_dir) / "pull-forward", 'r') as pull_forward:
418 for line in pull_forward:
403 stripped_line = line.strip() 419 stripped_line = line.strip()
404 if stripped_line: 420 if stripped_line:
405 [hours, datestr] = stripped_line.split(' ') 421 [hours, datestr] = stripped_line.split(' ')
@@ -420,15 +436,21 @@ class Worktime(object):
420 if not d == datetime.strptime(c, date_format).replace(tzinfo=tzlocal()).date(): break 436 if not d == datetime.strptime(c, date_format).replace(tzinfo=tzlocal()).date(): break
421 else: 437 else:
422 if d >= self.end_date.date(): 438 if d >= self.end_date.date():
423 self.pull_forward[d] = min(timedelta(hours = float(hours)), self.time_per_day(d) - (holidays[d] if d in holidays else timedelta())) 439 time_for_day = self.time_per_day(d) if d.isoweekday() in self.workdays else timedelta()
440 if d in self.extra_days_to_work:
441 time_for_day += self.extra_days_to_work[d]
442 self.pull_forward[d] = min(timedelta(hours = float(hours)), time_for_day)
424 except IOError as e: 443 except IOError as e:
425 if e.errno != 2: 444 if e.errno != 2:
426 raise e 445 raise e
427 446
447 for year in range(self.end_date.year + 1, max(self.pull_forward.keys()).year + 1):
448 holidays |= {k: v * timedelta(hours = hours_per_week(k)) / len(self.workdays) for k, v in Worktime.holidays(year).items()}
449
428 self.days_to_work = dict() 450 self.days_to_work = dict()
429 451
430 if self.pull_forward: 452 # if self.pull_forward:
431 end_day = max(end_day, max(list(self.pull_forward))) 453 # end_day = max(end_day, max(self.pull_forward.keys()))
432 454
433 for day in [start_day + timedelta(days = x) for x in range(0, (end_day - start_day).days + 1)]: 455 for day in [start_day + timedelta(days = x) for x in range(0, (end_day - start_day).days + 1)]:
434 if day.isoweekday() in self.workdays: 456 if day.isoweekday() in self.workdays:
@@ -436,26 +458,6 @@ class Worktime(object):
436 if time_to_work > timedelta(): 458 if time_to_work > timedelta():
437 self.days_to_work[day] = time_to_work 459 self.days_to_work[day] = time_to_work
438 460
439 self.extra_days_to_work = dict()
440
441 try:
442 with open(Path(config_dir) / "days-to-work", 'r') as extra_days_to_work_file:
443 for line in extra_days_to_work_file:
444 stripped_line = line.strip()
445 if stripped_line:
446 splitLine = stripped_line.split(' ')
447 if len(splitLine) == 2:
448 [hours, datestr] = splitLine
449 day = datetime.strptime(datestr, date_format).replace(tzinfo=tzlocal()).date()
450 self.extra_days_to_work[day] = timedelta(hours = float(hours))
451 else:
452 day = datetime.strptime(stripped_line, date_format).replace(tzinfo=tzlocal()).date()
453 self.extra_days_to_work[day] = self.time_per_day(day)
454 except IOError as e:
455 if e.errno != 2:
456 raise e
457
458
459 self.now_is_workday = self.is_workday(self.now.date()) 461 self.now_is_workday = self.is_workday(self.now.date())
460 462
461 self.time_worked = timedelta() 463 self.time_worked = timedelta()
@@ -471,9 +473,9 @@ class Worktime(object):
471 473
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()) 474 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(self.pull_forward) if d > self.end_date.date()]: 475 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 self.pull_forward or d == self.end_date.date())]) 476 days_forward = set([d for d in [start_day + timedelta(days = x) for x in range(0, (day - start_day).days + 1)] if d >= self.end_date.date() and d < day and (d not 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 self.pull_forward or d == self.end_date.date())]) 477 extra_days_forward = set([d for d in self.extra_days_to_work.keys() if d >= self.end_date.date() and d < day and (d not in self.pull_forward or d == self.end_date.date())])
476 days_forward = days_forward.union(extra_days_forward) 478 days_forward |= extra_days_forward
477 479
478 extra_day_time_left = timedelta() 480 extra_day_time_left = timedelta()
479 for extra_day in extra_days_forward: 481 for extra_day in extra_days_forward:
@@ -486,17 +488,30 @@ class Worktime(object):
486 day_time = max(timedelta(), self.time_per_day(extra_day) - self.extra_days_to_work[extra_day]) 488 day_time = max(timedelta(), self.time_per_day(extra_day) - self.extra_days_to_work[extra_day])
487 self.extra_days_to_work[extra_day] += extra_day_time * (day_time / extra_day_time_left) 489 self.extra_days_to_work[extra_day] += extra_day_time * (day_time / extra_day_time_left)
488 490
489 hours_per_day_forward = time_forward / len(days_forward) if len(days_forward) > 0 else timedelta() 491 def days_count(days_forward):
492 r = 0
493 for day in sorted(days_forward):
494 day_time = timedelta()
495 if day in self.extra_days_to_work:
496 day_time += self.extra_days_to_work
497 if day in holidays and not day in self.extra_days_to_work:
498 day_time -= holidays[day]
499 if day.isoweekday() in self.workdays:
500 day_time += timedelta(hours = hours_per_week(day)) / len(self.workdays)
501 r += max(timedelta(), day_time) / (timedelta(hours = hours_per_week(day)) / len(self.workdays))
502 return r
503
504 hours_per_day_forward = time_forward / days_count(days_forward) if days_count(days_forward) > 0 else timedelta()
490 days_forward.discard(self.end_date.date()) 505 days_forward.discard(self.end_date.date())
491 506
492 self.time_pulled_forward += time_forward - hours_per_day_forward * len(days_forward) 507 self.time_pulled_forward += time_forward - hours_per_day_forward * days_count(days_forward)
493 508
494 if self.end_date.date() in self.extra_days_to_work: 509 if self.end_date.date() in self.extra_days_to_work:
495 self.time_pulled_forward += self.extra_days_to_work[self.end_date.date()] 510 self.time_pulled_forward += self.extra_days_to_work[self.end_date.date()]
496 511
497 self.time_to_work += self.time_pulled_forward 512 # self.time_to_work += self.time_pulled_forward
498 513
499 self.time_worked += api.get_billable_hours(self.start_date, self.now, rounding = config.get("WORKTIME", {}).get("rounding", True)) 514 self.time_worked += api.get_billable_hours(self.start_date, self.now)
500 515
501def format_days(worktime, days, date_format=None): 516def format_days(worktime, days, date_format=None):
502 if not date_format: 517 if not date_format:
@@ -573,7 +588,7 @@ def worktime(pull_forward_cutoff, waybar, **args):
573 pull_forward_sum = sum(worktime.pull_forward.values(), start=timedelta(milliseconds=0)) 588 pull_forward_sum = sum(worktime.pull_forward.values(), start=timedelta(milliseconds=0))
574 if pull_forward_sum >= min(pull_forward_cutoff, timedelta(seconds = 1)): 589 if pull_forward_sum >= min(pull_forward_cutoff, timedelta(seconds = 1)):
575 worktime_no_pulled_forward = deepcopy(worktime) 590 worktime_no_pulled_forward = deepcopy(worktime)
576 worktime_no_pulled_forward.time_to_work -= worktime_no_pulled_forward.time_pulled_forward 591 # worktime_no_pulled_forward.time_to_work -= worktime_no_pulled_forward.time_pulled_forward
577 worktime_no_pulled_forward.time_pulled_forward = timedelta() 592 worktime_no_pulled_forward.time_pulled_forward = timedelta()
578 worktime_no_pulled_forward.pull_forward = dict() 593 worktime_no_pulled_forward.pull_forward = dict()
579 worktime.time_to_work += pull_forward_sum 594 worktime.time_to_work += pull_forward_sum
@@ -611,7 +626,7 @@ def time_worked(now, waybar, **args):
611 out_text = None 626 out_text = None
612 out_class = "running" if now.running_entry else "stopped" 627 out_class = "running" if now.running_entry else "stopped"
613 tooltip = tooltip_timedelta(worked) 628 tooltip = tooltip_timedelta(worked)
614 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())); 629 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()))
615 difference = target_time - worked 630 difference = target_time - worked
616 difference_pull_forward = difference + now.time_pulled_forward 631 difference_pull_forward = difference + now.time_pulled_forward
617 if now.running_entry and difference_pull_forward < timedelta(seconds=0): 632 if now.running_entry and difference_pull_forward < timedelta(seconds=0):
@@ -886,7 +901,7 @@ def main():
886 901
887 config = Worktime.config() 902 config = Worktime.config()
888 903
889 parser = argparse.ArgumentParser(prog = "worktime", description = 'Track worktime using toggl API') 904 parser = argparse.ArgumentParser(prog = "worktime", description = 'Track worktime using Kimai API')
890 parser.add_argument('--time', dest = 'now', metavar = 'TIME', type = isotime, help = 'Time to calculate status for (default: current time)', default = datetime.now(tzlocal())) 905 parser.add_argument('--time', dest = 'now', metavar = 'TIME', type = isotime, help = 'Time to calculate status for (default: current time)', default = datetime.now(tzlocal()))
891 parser.add_argument('--start', dest = 'start_datetime', metavar = 'TIME', type = isotime, help = 'Time to calculate status from (default: None)', default = None) 906 parser.add_argument('--start', dest = 'start_datetime', metavar = 'TIME', type = isotime, help = 'Time to calculate status from (default: None)', default = None)
892 parser.add_argument('--no-running', dest = 'include_running', action = 'store_false') 907 parser.add_argument('--no-running', dest = 'include_running', action = 'store_false')
@@ -923,5 +938,139 @@ def main():
923 938
924 args.cmd(**vars(args)) 939 args.cmd(**vars(args))
925 940
941async def ui_update_options(api, cache_path):
942 options = set()
943 sort_order = dict()
944 entry_iter = enumerate(api.get_recent_entries())
945 loop = asyncio.get_event_loop()
946 start = clock_gettime_ns(CLOCK_MONOTONIC)
947 while item := await loop.run_in_executor(None, next, entry_iter):
948 ix, entry = item
949 if len(options) >= 20 or ix >= 1000:
950 break
951 elif len(options) >= 3:
952 now = clock_gettime_ns(CLOCK_MONOTONIC)
953 if now - start >= 4000000000:
954 break
955
956 option = frozendict({
957 'tags': frozenset(entry['tags']),
958 'activity': frozendict({'id': entry['activity']['id'], 'name': entry['activity']['name']}),
959 'project': frozendict({'id': entry['project']['id'], 'customer': entry['project']['customer']['name'], 'name': entry['project']['name']}),
960 'description': entry['description'] if entry['description'] else None,
961 'billable': entry['billable'],
962 })
963 sort_value = isoparse(entry['begin'])
964 if option in sort_order:
965 sort_value = max(sort_value, sort_order[option])
966 sort_order[option] = sort_value
967 options.add(option)
968
969 options = list(sorted(options, key = lambda o: sort_order[o], reverse = True))
970
971 with AtomicWriter(cache_path, overwrite=True) as ch:
972 ch.write_text(jsonpickle.encode(options))
973
974 return options
975
976def ui_render_option(option):
977 res = ''
978 if option['description']:
979 res += '„{}“, '.format(option['description'])
980 res += option['activity']['name'] + ', '
981 res += option['project']['name']
982 if option['project']['customer'] not in option['project']['name']:
983 res += ' ({})'.format(option['project']['customer'])
984 if option['tags']:
985 res += ', {}'.format(' '.join(map(lambda t: '#{}'.format(t), option['tags'])))
986 if not option['billable']:
987 res += ', not billable'
988 return res
989
990async def ui_main():
991 cache_path = Path(BaseDirectory.save_cache_path('worktime-ui')) / 'options.json'
992 options = None
993 try:
994 with cache_path.open('r', encoding='utf-8') as ch:
995 options = jsonpickle.decode(ch.read())
996 except FileNotFoundError:
997 pass
998
999 config = Worktime.config()
1000 api = KimaiAPI(
1001 base_url=config.get("KIMAI", {}).get("BaseUrl", None),
1002 api_token=config.get("KIMAI", {}).get("ApiToken", None),
1003 clients=config.get("KIMAI", {}).get("Clients", None)
1004 )
1005 running_entry = api.get_running_entry()
1006
1007 async with asyncio.TaskGroup() as tg:
1008 update_options = tg.create_task(ui_update_options(api, cache_path))
1009 if not options:
1010 options = await update_options
1011
1012 read_fd, write_fd = os.pipe()
1013 w_pipe = open(write_fd, 'wb', 0)
1014 loop = asyncio.get_event_loop()
1015 w_transport, _ = await loop.connect_write_pipe(
1016 asyncio.Protocol,
1017 w_pipe,
1018 )
1019 r_pipe = open(read_fd, 'rb', 0)
1020
1021 proc = await asyncio.create_subprocess_exec(
1022 "fuzzel", "--dmenu", "--index", "--width=60",
1023 stdout = asyncio.subprocess.PIPE,
1024 stdin = r_pipe,
1025 )
1026
1027 with closing(w_transport) as t:
1028 if running_entry:
1029 t.write(b'Stop running timesheet\n')
1030 for option in options:
1031 t.write(ui_render_option(option).encode('utf-8') + b'\n')
1032
1033 stdout, _ = await proc.communicate()
1034 if proc.returncode != 0:
1035 return
1036 fuzzel_out = int(stdout.decode('utf-8'))
1037 if fuzzel_out < 0 or fuzzel_out >= len(options):
1038 return
1039 elif running_entry and fuzzel_out == 0:
1040 api.stop_clock(running_entry['id'])
1041 await notify.Server('worktime').Notify("Stopped running timesheet").set_timeout(65000).show()
1042 else:
1043 if running_entry:
1044 fuzzel_out -= 1
1045 option = options[fuzzel_out]
1046 api.start_clock(
1047 project_id = option['project']['id'],
1048 activity_id = option['activity']['id'],
1049 description = option['description'],
1050 tags = option['tags'],
1051 billable = option['billable'],
1052 )
1053 await notify.Server('worktime').Notify("Timesheet started…").set_timeout(65000).show()
1054
1055
1056def ui():
1057 asyncio.run(ui_main())
1058
1059async def stop_main():
1060 config = Worktime.config()
1061 api = KimaiAPI(
1062 base_url=config.get("KIMAI", {}).get("BaseUrl", None),
1063 api_token=config.get("KIMAI", {}).get("ApiToken", None),
1064 clients=config.get("KIMAI", {}).get("Clients", None)
1065 )
1066 if running_entry := api.get_running_entry():
1067 api.stop_clock(running_entry['id'])
1068 await notify.Server('worktime').Notify("Stopped running timesheet").set_timeout(65000).show()
1069 else:
1070 await notify.Server('worktime').Notify("No timesheet currently running").set_timeout(65000).show()
1071
1072def stop():
1073 asyncio.run(stop_main())
1074
926if __name__ == "__main__": 1075if __name__ == "__main__":
927 sys.exit(main()) 1076 sys.exit(main())
diff --git a/shell.nix b/shell.nix
index bec68474..1b334187 100644
--- a/shell.nix
+++ b/shell.nix
@@ -3,6 +3,12 @@ let
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
@@ -15,5 +21,9 @@ in pkgs.mkShell {
15 nvfetcher.packages.${system}.default 21 nvfetcher.packages.${system}.default
16 ca-util.packages.${system}.ca 22 ca-util.packages.${system}.ca
17 poetry uv 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/bcachefs.nix b/system-profiles/bcachefs.nix
index f9f048b9..6090c56d 100644
--- a/system-profiles/bcachefs.nix
+++ b/system-profiles/bcachefs.nix
@@ -1,4 +1,4 @@
1{ pkgs, ... } : { 1{ pkgs, lib, ... } : {
2 config = { 2 config = {
3 boot.supportedFilesystems.bcachefs = true; 3 boot.supportedFilesystems.bcachefs = true;
4 environment.systemPackages = with pkgs; [ bcachefs-tools ]; 4 environment.systemPackages = with pkgs; [ bcachefs-tools ];
diff --git a/system-profiles/core/default.nix b/system-profiles/core/default.nix
index 229a007e..e5f9dc16 100644
--- a/system-profiles/core/default.nix
+++ b/system-profiles/core/default.nix
@@ -180,13 +180,7 @@ in {
180 }; 180 };
181 environment.systemPackages = with pkgs; [ git-annex scutiger ]; 181 environment.systemPackages = with pkgs; [ git-annex scutiger ];
182 } 182 }
183 ] ++ (optional (options ? system.switch.enableNg) { 183 ] ++ (optional (options ? system.rebuild.enableNg) {
184 system.switch = lib.mkDefault {
185 enable = false;
186 enableNg = true;
187 };
188 })
189 ++ (optional (options ? system.rebuild.enableNg) {
190 system.rebuild.enableNg = lib.mkDefault true; 184 system.rebuild.enableNg = lib.mkDefault true;
191 }) 185 })
192 ++ (optional (options ? services.userborn) { 186 ++ (optional (options ? services.userborn) {
diff --git a/system-profiles/lanzaboote.nix b/system-profiles/lanzaboote.nix
new file mode 100644
index 00000000..f1e179cf
--- /dev/null
+++ b/system-profiles/lanzaboote.nix
@@ -0,0 +1,14 @@
1{ flakeInputs, pkgs, ... }:
2{
3 imports = [
4 flakeInputs.lanzaboote.nixosModules.lanzaboote
5 ];
6
7 config = {
8 environment.systemPackages = [ pkgs.sbctl ];
9 boot.lanzaboote = {
10 enable = true;
11 pkiBundle = "/var/lib/sbctl";
12 };
13 };
14}
diff --git a/system-profiles/nfsroot.nix b/system-profiles/nfsroot.nix
index b0116d61..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
@@ -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/zfs.nix b/system-profiles/zfs.nix
index a93dddd2..af9f1c17 100644
--- a/system-profiles/zfs.nix
+++ b/system-profiles/zfs.nix
@@ -1,4 +1,4 @@
1{ pkgs, lib, ... } : { 1{ config, pkgs, lib, ... } : {
2 config = { 2 config = {
3 boot = { 3 boot = {
4 kernelPackages = pkgs.linuxPackages_6_12; 4 kernelPackages = pkgs.linuxPackages_6_12;
diff --git a/user-profiles/tmux/default.nix b/user-profiles/tmux/default.nix
index dc4e791f..7ea0c0d5 100644
--- a/user-profiles/tmux/default.nix
+++ b/user-profiles/tmux/default.nix
@@ -16,8 +16,8 @@
16 16
17 installPhase = '' 17 installPhase = ''
18 substitute $src $out \ 18 substitute $src $out \
19 --subst-var-by zsh ${config.programs.zsh.package} \ 19 --subst-var-by zsh ${lib.getExe config.programs.zsh.package} \
20 --subst-var-by man ${config.programs.man.package} 20 --subst-var-by man ${lib.getExe config.programs.man.package}
21 ''; 21 '';
22 }); 22 });
23 }; 23 };
diff --git a/user-profiles/utils.nix b/user-profiles/utils.nix
index da79e336..edf6da11 100644
--- a/user-profiles/utils.nix
+++ b/user-profiles/utils.nix
@@ -46,6 +46,8 @@ in {
46 lesspipe.enable = true; 46 lesspipe.enable = true;
47 47
48 man.enable = true; 48 man.enable = true;
49
50 vim.enable = true;
49 }; 51 };
50 52
51 home.sessionVariables = { 53 home.sessionVariables = {
diff --git a/user-profiles/yt-dlp.nix b/user-profiles/yt-dlp.nix
index ef0be87e..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";
@@ -29,6 +30,7 @@
29 # ]; 30 # ];
30 remux-video = "mp4>mkv"; 31 remux-video = "mp4>mkv";
31 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\""; 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 };