From 9d6412a1fa22ceb3813c482deeb6a5bdd42da07a Mon Sep 17 00:00:00 2001 From: Adam0 Date: Sun, 05 Jul 2026 17:17:00 +0000 Subject: [PATCH] jututsu init --- LICENSE | 6 +++--- flake.lock | 142 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------------------------------------------------- flake.nix | 4 ++-- secrets.yaml | 9 +++++++-- lib/starship.nix | 43 ++++++++++++++++++++++++++++++++++++++++++- .tangled/workflows/checks.yml | 2 +- modules/pkgs/default.nix | 1 + modules/pkgs/rclone-bisync-runner.nix | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ modules/profiles/base.nix | 4 ++++ modules/profiles/personal.nix | 5 +++++ modules/programs/delta.nix | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ modules/programs/git.nix | 53 ++--------------------------------------------------- modules/programs/jujutsu.nix | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ modules/services/ananicy.nix | 10 ---------- modules/services/apprise.nix | 50 -------------------------------------------------- modules/services/authentik.nix | 77 ----------------------------------------------------------------------------- modules/services/avahi.nix | 13 ------------- modules/services/bluetooth.nix | 17 ----------------- modules/services/bpftune.nix | 5 ----- modules/services/btrfs-autoscrub.nix | 7 ------- modules/services/cloudbeaver.nix | 115 ------------------------------------------------------------------------------------------------------------------- modules/services/crowdsec.nix | 386 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- modules/services/dockhand.nix | 98 -------------------------------------------------------------------------------------------------- modules/services/envfs.nix | 27 --------------------------- modules/services/evolution-data-server.nix | 5 ----- modules/services/firewall.nix | 5 ----- modules/services/flaresolverr.nix | 9 --------- modules/services/flatpak.nix | 42 ------------------------------------------ modules/services/geoclue.nix | 20 -------------------- modules/services/gnome-keyring.nix | 20 -------------------- modules/services/godns.nix | 129 --------------------------------------------------------------------------------------------------------------------------------- modules/services/gotify.nix | 133 ------------------------------------------------------------------------------------------------------------------------------------- modules/services/gvfs.nix | 11 ----------- modules/services/hawser.nix | 69 --------------------------------------------------------------------- modules/services/libinput.nix | 20 -------------------- modules/services/locate.nix | 9 --------- modules/services/mysql.nix | 90 ------------------------------------------------------------------------------------------ modules/services/network.nix | 85 ------------------------------------------------------------------------------------- modules/services/pipewire.nix | 19 ------------------- modules/services/podman.nix | 62 -------------------------------------------------------------- modules/services/postgres.nix | 125 ----------------------------------------------------------------------------------------------------------------------------- modules/services/power-profiles-daemon.nix | 5 ----- modules/services/printing.nix | 29 ----------------------------- modules/services/proton-wireguard.nix | 223 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- modules/services/scx-loader.nix | 15 --------------- modules/services/ssh-stunnel.nix | 16 ---------------- modules/services/ssh.nix | 78 ------------------------------------------------------------------------------ modules/services/timesyncd.nix | 12 ------------ modules/services/tlp.nix | 15 --------------- modules/services/tmp.nix | 5 ----- modules/services/traefik.nix | 284 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- modules/services/udisks2.nix | 5 ----- modules/services/upower.nix | 5 ----- modules/services/wifi.nix | 17 ----------------- modules/services/wireguard.nix | 119 ----------------------------------------------------------------------------------------------------------------------- modules/services/zram.nix | 8 -------- modules/desktop/hyprland/default.nix | 2 +- modules/pkgs/preview/text.nix | 4 +++- modules/profiles/stylix/server.nix | 18 ++++++++++-------- modules/programs/cli/gen-license.nix | 5 +++++ modules/programs/cli/gh.nix | 11 ++++++++++- modules/programs/gui/tangled.nix | 33 +++++++++++++++++++++++++++++++++ modules/services/data/btrfs-autoscrub.nix | 7 +++++++ modules/services/data/mysql.nix | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ modules/services/data/postgres.nix | 125 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ modules/services/data/rclone.nix | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ modules/services/desktop/envfs.nix | 27 +++++++++++++++++++++++++++ modules/services/desktop/evolution-data-server.nix | 5 +++++ modules/services/desktop/flatpak.nix | 42 ++++++++++++++++++++++++++++++++++++++++++ modules/services/desktop/geoclue.nix | 20 ++++++++++++++++++++ modules/services/desktop/gnome-keyring.nix | 20 ++++++++++++++++++++ modules/services/desktop/gvfs.nix | 11 +++++++++++ modules/services/desktop/locate.nix | 9 +++++++++ modules/services/desktop/tmp.nix | 5 +++++ modules/services/desktop/udisks2.nix | 5 +++++ modules/services/hardware/bluetooth.nix | 17 +++++++++++++++++ modules/services/hardware/libinput.nix | 20 ++++++++++++++++++++ modules/services/hardware/pipewire.nix | 19 +++++++++++++++++++ modules/services/hardware/power-profiles-daemon.nix | 5 +++++ modules/services/hardware/printing.nix | 29 +++++++++++++++++++++++++++++ modules/services/hardware/tlp.nix | 15 +++++++++++++++ modules/services/hardware/upower.nix | 5 +++++ modules/services/network/avahi.nix | 13 +++++++++++++ modules/services/network/godns.nix | 129 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ modules/services/network/network.nix | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ modules/services/network/proton-wireguard.nix | 223 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ modules/services/network/ssh-stunnel.nix | 16 ++++++++++++++++ modules/services/network/ssh.nix | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ modules/services/network/timesyncd.nix | 12 ++++++++++++ modules/services/network/wifi.nix | 17 +++++++++++++++++ modules/services/network/wireguard.nix | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ modules/services/performance/ananicy.nix | 10 ++++++++++ modules/services/performance/bpftune.nix | 5 +++++ modules/services/performance/scx-loader.nix | 15 +++++++++++++++ modules/services/performance/zram.nix | 8 ++++++++ modules/services/security/authentik.nix | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ modules/services/security/crowdsec.nix | 386 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ modules/services/security/firewall.nix | 5 +++++ modules/services/server/apprise.nix | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ modules/services/server/cloudbeaver.nix | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ modules/services/server/dockhand.nix | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ modules/services/server/flaresolverr.nix | 9 +++++++++ modules/services/server/gotify.nix | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ modules/services/server/hawser.nix | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ modules/services/server/podman.nix | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ modules/services/server/traefik.nix | 284 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ modules/desktop/hyprland/keybinds/screenshots-and-color.nix | 2 +- modules/programs/cli/starship/format.nix | 9 +-------- modules/programs/cli/starship/git.nix | 47 ----------------------------------------------- modules/programs/cli/starship/jujutsu.nix | 168 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ modules/programs/gui/ghostty/settings.nix | 26 ++++++++++++++++++-------- modules/programs/gui/zen/search.nix | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------------------------- modules/programs/tui/neovim/components/dashboard.nix | 12 +++++++++--- 113 file(s) changed, 3229 insertion(s)(+), 2749 deletion(s)(-) diff --git a/LICENSE b/LICENSE --- a/LICENSE +++ b/LICENSE @@ -630,11 +630,11 @@ the "copyright" line and a pointer to where the full notice is found. - Copyright (C) + Copyright (C) 2026 Adam0 This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, diff --git a/flake.lock b/flake.lock --- a/flake.lock +++ b/flake.lock @@ -48,11 +48,11 @@ "uv2nix": "uv2nix" }, "locked": { - "lastModified": 1782385544, - "narHash": "sha256-b9k01gZV57mbfd2unu3hU9uROeG/9MdHZX7CGJpYHUs=", + "lastModified": 1783089004, + "narHash": "sha256-DQFZGbgeHSKkztG9Sy72mfrBxHq87XOysLgGT95a8As=", "owner": "nix-community", "repo": "authentik-nix", - "rev": "c2144eb8a943341da3f7982e663b060b3b39e657", + "rev": "d08d2c853ee68752b225ede0fa770b8fe131d7ec", "type": "github" }, "original": { @@ -165,11 +165,11 @@ "cachyos-kernel": { "flake": false, "locked": { - "lastModified": 1781883168, - "narHash": "sha256-raAojJGk0aWdscfFn/9ikZ6V5oUuAZcAz5kjAZ2QN3E=", + "lastModified": 1782811891, + "narHash": "sha256-xLfMSeSKKHmYCESe9Ca24eSXUsEV5z1oC59Cqfb7s8U=", "owner": "CachyOS", "repo": "linux-cachyos", - "rev": "daed450e9b1a4fadfef68fb4fa5e2f3391fedb34", + "rev": "dc2bfb6686ce41d04185d1a3a11b2b6fc24aeca6", "type": "github" }, "original": { @@ -181,11 +181,11 @@ "cachyos-kernel-patches": { "flake": false, "locked": { - "lastModified": 1782242233, - "narHash": "sha256-AUwTZq++PBq0qjDVFKqD0AZNNwa0b1RK41bM9XMbkW8=", + "lastModified": 1782814529, + "narHash": "sha256-YGYe7cy8vUFguQzdCt6FP1B/viF53cJdFZhNJ8Rpp5Y=", "owner": "CachyOS", "repo": "kernel-patches", - "rev": "19250dcc39862169961756c733b8a6ba77754c22", + "rev": "f98908d8b5cacc4c24a6039ffd9f41f6a0de4ba2", "type": "github" }, "original": { @@ -503,11 +503,11 @@ ] }, "locked": { - "lastModified": 1778716662, - "narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=", + "lastModified": 1782949081, + "narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb", + "rev": "17c9d6cdfc60c64f4ee8d306f9bc0b4ccb51481e", "type": "github" }, "original": { @@ -521,11 +521,11 @@ "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1778716662, - "narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=", + "lastModified": 1782949081, + "narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb", + "rev": "17c9d6cdfc60c64f4ee8d306f9bc0b4ccb51481e", "type": "github" }, "original": { @@ -665,11 +665,11 @@ ] }, "locked": { - "lastModified": 1782839684, - "narHash": "sha256-vzs4SBgPsK4aNzlJR2PpFwtARazXMOxZonQnDz0YHxk=", + "lastModified": 1783099620, + "narHash": "sha256-prQSM9PGnrfSaqknJOZ3DCQKbpMiXAWLVC5SHbjLcJ4=", "owner": "nix-community", "repo": "home-manager", - "rev": "2a37d71bbe69e1522ddabf03a4cea0374958bdbe", + "rev": "b2e4390ff35319c52935d6a700b615da4c0f204d", "type": "github" }, "original": { @@ -695,17 +695,17 @@ ] }, "locked": { - "lastModified": 1780858356, - "narHash": "sha256-3imyFQXBp08TMWMVljPsYXjBFCdtLI5qwmHh7tHxQQg=", - "owner": "adam01110", - "repo": "hylix", - "rev": "86af0f084df78612981136a7389f033b3bb3b9a6", - "type": "github" + "lastModified": 1783119574, + "narHash": "sha256-jV2wdadHsBH4wNRSmxj0/KIXsmOhWzB6n+aVMRDjLeU=", + "ref": "refs/heads/main", + "rev": "ba06fe85d5708340d5bbbc6eeaca5da50f7e64e2", + "revCount": 14, + "type": "git", + "url": "https://tangled.org/adam0.dev/hylix" }, "original": { - "owner": "adam01110", - "repo": "hylix", - "type": "github" + "type": "git", + "url": "https://tangled.org/adam0.dev/hylix" } }, "hyprcursor": { @@ -1145,11 +1145,11 @@ "nixpkgs": "nixpkgs_5" }, "locked": { - "lastModified": 1782415778, - "narHash": "sha256-Qts73QQA+lADfxWjonL3Q1JcZssVZPsQI38L3qZyS0o=", + "lastModified": 1783018940, + "narHash": "sha256-fQ4+88WbgRkQYCMF37GYNuthHojlYhvfIAKJpSynVyk=", "owner": "xddxdd", "repo": "nix-cachyos-kernel", - "rev": "1740ec90e7b07730c212a3a1ff5e71af08a5270b", + "rev": "3a4f4055db5f96ce696b5704c996d5bffbcda58d", "type": "github" }, "original": { @@ -1203,11 +1203,11 @@ ] }, "locked": { - "lastModified": 1782636943, - "narHash": "sha256-ripjZa7BBLwL1uS5VJF3s/VpZpWt5ZIQEvkJ/FJNpQw=", + "lastModified": 1783023993, + "narHash": "sha256-gaOvvY/lL1eWoSmSRO17pWry8R49AuPMrF4nzu47K5o=", "owner": "nix-community", "repo": "nix-index-database", - "rev": "058b1f9381fa79fcda49982370a750ff92dbba43", + "rev": "f8ed6cdcb1fd28a6ab7b61f4467a4f67fe2d9074", "type": "github" }, "original": { @@ -1237,11 +1237,11 @@ ] }, "locked": { - "lastModified": 1783101455, - "narHash": "sha256-4E+DNHGPLuLU0gq4fiFNqj0p+IdIrCzL1tH5F12iFpc=", + "lastModified": 1783109830, + "narHash": "sha256-eAMJWngshgYl9PKfJus6R/HsKH8dVMgXUgUEuKqPoDA=", "ref": "refs/heads/main", - "rev": "a3c2693d9f5244f6ecd32e74602a2363bb76f01c", - "revCount": 80, + "rev": "9981ffb572c82154e244003cbf5766bbb3f17b06", + "revCount": 86, "type": "git", "url": "https://tangled.org/adam0.dev/nix-userstyles" }, @@ -1260,11 +1260,11 @@ "nixpkgs-nixcord": "nixpkgs-nixcord" }, "locked": { - "lastModified": 1782838476, - "narHash": "sha256-SLsFaWwN/5qJNgq4F87yK64KU+jXRUHqSUHRau38AWw=", + "lastModified": 1783074296, + "narHash": "sha256-PhKjyxSpIEf1kNvySyoDx4XNYWghi69KwqH+AnTxO7Y=", "owner": "kaylorben", "repo": "nixcord", - "rev": "e055aaffc81400b8b59495ed8ddb4cd6401d3850", + "rev": "5f428154cf5d148f15c602f3afd04983e68ef364", "type": "github" }, "original": { @@ -1275,11 +1275,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1781577229, - "narHash": "sha256-lrp67w8AulE9Ks53n27I45ADSzbOCn4H+CNW1Ck8B+8=", + "lastModified": 1782467914, + "narHash": "sha256-pGvFkM8N0xEkIIXDe5YYfbEAvHrk4IxBrjB/x8OomhE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "567a49d1913ce81ac6e9582e3553dd90a955875f", + "rev": "e73de5be04e0eff4190a1432b946d469c794e7b4", "type": "github" }, "original": { @@ -1339,11 +1339,11 @@ }, "nixpkgs-lib": { "locked": { - "lastModified": 1777168982, - "narHash": "sha256-GOkGPcboWE9BmGCRMLX3worL4EMnsnG8MyKmXNeYuhQ=", + "lastModified": 1782614948, + "narHash": "sha256-ePjCwr1sNm9NYUqywL7QfK3JnlS015msC+eBu2zKlp8=", "owner": "nix-community", "repo": "nixpkgs.lib", - "rev": "f5901329dade4a6ea039af1433fb087bd9c1fe14", + "rev": "db3f255737b94216eb71cce308e2912cf6bc2d7c", "type": "github" }, "original": { @@ -1430,11 +1430,11 @@ }, "nixpkgs_5": { "locked": { - "lastModified": 1782378976, - "narHash": "sha256-UqQgBlQATXM3aBvzTRE/1wxHrCdKg5/ePlXfG/7Eqd8=", + "lastModified": 1783003425, + "narHash": "sha256-CLVsEepcFedoiIwXVlAYam9bbTt3mmTJXgGrvoLDDFM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "5df71f3d167f0aad71658608361c1301147b9eb6", + "rev": "6147971a7160e60cf691651d97cc8411395f5d90", "type": "github" }, "original": { @@ -1462,11 +1462,11 @@ }, "nixpkgs_7": { "locked": { - "lastModified": 1782760764, - "narHash": "sha256-L3gmggVc+GxfmG8b6bWL0wkiniDNppQRsfaKc0IfyRA=", - "rev": "7a1a64774a5fd0b0cd39ac95d0e170ace8b266a0", + "lastModified": 1782918843, + "narHash": "sha256-mFP086y1bNA1g9AsY/pCue3H3W2R7ayroHyRbZrcMf0=", + "rev": "e8273b29fe1390ec8d4603f2477357555291432e", "type": "tarball", - "url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.11pre1024597.7a1a64774a5f/nixexprs.tar.xz" + "url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.11pre1025900.e8273b29fe13/nixexprs.tar.xz" }, "original": { "type": "tarball", @@ -1483,11 +1483,11 @@ ] }, "locked": { - "lastModified": 1780949817, - "narHash": "sha256-2oJuPyt+4dd+ZzO7TFqpmkSAAYpRg9SF4eV8kJGl6Tk=", + "lastModified": 1783017890, + "narHash": "sha256-bAzrtN0GDMH0dOZkgeI3zSxfFs2rdD3pMuzOxvTF0bs=", "owner": "noctalia-dev", "repo": "noctalia", - "rev": "f816591afc2f2f606d1f0cf70b51e95c04a7a8aa", + "rev": "a48885b9fec485c903c955749a7da6e30147cd38", "type": "github" }, "original": { @@ -1529,11 +1529,11 @@ ] }, "locked": { - "lastModified": 1782861039, - "narHash": "sha256-4Lo1FCtYCpSfpbY5xznypLyQ7MqpZto3AjK21oecgqA=", + "lastModified": 1783116538, + "narHash": "sha256-/utah2I8EyJiZg0PWbXtT3cNoYhiu90jJktnOLRO5Sc=", "owner": "nix-community", "repo": "NUR", - "rev": "7e683e9f863048d51e8edb19199d5e01c6373e13", + "rev": "d91274fa07b67388f77900181514ea567e0bec8a", "type": "github" }, "original": { @@ -1757,11 +1757,11 @@ ] }, "locked": { - "lastModified": 1782165805, - "narHash": "sha256-478kKQBvK6SYTOdN2h9jhKJv94nbXRbFMfuL1WshErg=", + "lastModified": 1783104586, + "narHash": "sha256-vSLKc7m34/g5xH4dwmNZSqxc5OcHeE3qiG3EIz6bJKs=", "owner": "Mic92", "repo": "sops-nix", - "rev": "56b24064fdcaedca53553b1a6d607fd23b613a24", + "rev": "1ebd41717762d837d5115f7108522c29cdb00fbb", "type": "github" }, "original": { @@ -1778,11 +1778,11 @@ "systems": "systems_8" }, "locked": { - "lastModified": 1782633406, - "narHash": "sha256-JOSMk12GgnTLVlUSCuKkqyUF6cw82wl7t7e1WuFfg5s=", + "lastModified": 1782898510, + "narHash": "sha256-7QVN7ijsXbIBRrI9LdXfeoFktTS0I6HZya9tu6+STrs=", "owner": "Gerg-L", "repo": "spicetify-nix", - "rev": "5ff9a6ca9dcbad7cccea2c97d30237468b16feda", + "rev": "9cabea6f5973ec01f60080ea50f54f8f6d74dc95", "type": "github" }, "original": { @@ -1815,11 +1815,11 @@ "tinted-zed": "tinted-zed" }, "locked": { - "lastModified": 1782771270, - "narHash": "sha256-1W5Oga37XF1fzjpUut98j32NS9XcLQ5HmZDuOqSSrrY=", + "lastModified": 1783101802, + "narHash": "sha256-/Ti+wDco0f3C9s94RxyBKJyc0BklwYh0a/jFWKeHNYw=", "owner": "danth", "repo": "stylix", - "rev": "47571deb317bb1e53fbbe9c3cbf2a941cd94f794", + "rev": "718c14e8ecba215a65ff955c187fadb9732ddd01", "type": "github" }, "original": { @@ -2182,11 +2182,11 @@ ] }, "locked": { - "lastModified": 1782554936, - "narHash": "sha256-tH3MNTu/o2xzYXnRYsl9/Q5k6mjIrcqWZ+qbqzdN2L8=", + "lastModified": 1783033669, + "narHash": "sha256-9FuBZ4xiu0z/3s2+GKH98WV5Pu5z1gDNdhSXu5leQYE=", "owner": "0xc000022070", "repo": "zen-browser-flake", - "rev": "b3df24cd84ddecf5f13c48be9bdd99cf3bc7e1dc", + "rev": "0cd53b6953919cb6858db24fd7dc95714dcab2e7", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix --- a/flake.nix +++ b/flake.nix @@ -1,7 +1,7 @@ # DO-NOT-EDIT. This file was auto-generated using github:vic/flake-file. # Use `nix run .#write-flake` to regenerate it. { - outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } (inputs.import-tree ./modules); + outputs = inputs: inputs.flake-parts.lib.mkFlake {inherit inputs;} (inputs.import-tree ./modules); inputs = { authentik-nix = { @@ -23,7 +23,7 @@ inputs.nixpkgs.follows = "nixpkgs"; }; hylix = { - url = "github:adam01110/hylix"; + url = "git+https://tangled.org/adam0.dev/hylix"; inputs = { flake-parts.follows = "flake-parts"; import-tree.follows = "import-tree"; diff --git a/secrets.yaml b/secrets.yaml --- a/secrets.yaml +++ b/secrets.yaml @@ -81,6 +81,11 @@ client_secret: ENC[AES256_GCM,data:6PhFHPRcrUD1tPySDiB4tAtTng4/lYfNZ7XGYvIxxkbDobH9q3pjtuvXkz4MvGyjtC+M4sxc8qH1or7AYFtb/u71aptIwt+u821jz9XiFrQJiM7RC9yTZt0RQLtpPkTejRg4jfBXhDt+xOVkOZG7Z7cMrsMIS6eYDUJhpOXUO/c=,iv:6IseU28YQFyTmTh3dEUeK2pfvVq5CFIpXXYDkuY4HVY=,tag:NRZIDm07RhgDsEXT8IGl4Q==,type:str] desktop_token: ENC[AES256_GCM,data:LsEQ+gXlyXpl7BF6d+S1okv9t4OlMCk=,iv:Gq+Z5OiQR4cKy7+hM5JswAVtol+WX7nb9a7pNgmW5ZA=,tag:wz64fqoNyKmpGnIrjHt1mA==,type:str] qbittorrent_proxy_path: ENC[AES256_GCM,data:FGSBt2MQZ8Flq0y1VG0OYlrlKzweSaaiB+xKSWFObJXX2q/oNODo/7dPL8r29Fb79GKV7n5hXpQDLzSYOwd0A0EPijj/SAk=,iv:yJqKvC2L2O0eOTKvO/cAX1ldEx8z8GTvG6cbjdiXN+Q=,tag:cpFgkJoVXgfiTrLVS/KTdA==,type:str] +rclone: + copyparty: + basic_auth_header: ENC[AES256_GCM,data:LuCkGzj9LxVuJKT5lS7cXCJlmeqCrZGsP1ojlk17mHpesyqQEc/3s8pwGvNLNciTA2dGlYYcECyJKly40tclKYTRrM+1vYInAlz/usA9H4OCTY+HsQB/QTrNON2hkvn2daqI679jRZS7MpyC,iv:c1i+qaSh5z8Sse2TixyWKv19aAuwdq3zw/MvQwO2+es=,tag:CnuKAWn12/dpv28WtF0SMQ==,type:str] + google: + token: ENC[AES256_GCM,data:y9fZmPYszm6B0ucuKdEA3aWx33B3VuXYj9ajglUoqVXUIxbOiprEFKNGjl6yR7SxV0MVU3e81/3Q856otA3TkQhsboDteiJOE3hHrn/iJwqCiKzX2yKzkzann0VA2ArPWjnRVTp+CZOQZu868Qwm5yHWFPpnNtVm1ST12hQe6p1DB6HjLZVj66mtZmzqKdcRwL0hTLw35Zys/l7BHb/JrDbe87K7Z/lDwGKXZruL8xgIKfc51mgO69+MPCsA8zGdH74Xv+CzHhp4lT305bGEKGjrHJICExL2i3mzRnWWBv0NJVKIQUsLylEaDLxbpZkXWqqOyO9NtmMhACobQJFCxGFChjvFLzNivF6T3tjiUiH+Ka4VkndaOAB/BbYgUCPHFNhOJ5i+9Js2tPQE/CB+20SlFPm2jSIcJmS0iSSkypJiJVSZFSftOf4PIL0wRdmI6UkQzal89e07ebMUZTqi0F38qkZDAA8swyL21n+85N2/Myjg+cvsKzPnph0GSinZUqPb9sfkANYuYBQZc7fKjgHDwYJL4h+izgCfL0hTFNKPxteHYo2hirWs984FfSnDBO4luA7kkHNbcV1QZpapFxP0DMsEMtO27ciUHX7xjItKVkQIjP+c0rjSm2l56hfYjkw=,iv:u1efJOuHXZ9OS7cD4dTV7FjqZFlr+EahfxSfN8hzUns=,tag:sU499A/nucn3hoi8e37kKw==,type:str] sops: age: - enc: | @@ -119,8 +124,8 @@ w2ToACYVigKMSlqPNG9bKcK75XI/iAONG48DQm1Nse3yB9/q3jwd+Q== -----END AGE ENCRYPTED FILE----- recipient: age1yl52c6aemw9anmfuqayamvsnkvlu6fmy0kk3gzvrraexxd9pr90qvs8pe5 - lastmodified: "2026-06-30T23:06:57Z" - mac: ENC[AES256_GCM,data:aFDS7t6yZw9USgypvUkJFQXmdEGzTtoCVN0+Cnz0D9FVhi+CNNSCAtIMenf86hG4bUzVyCrTHgevpydS6dCs3vMwZZYXbyo2zz1jP2Ud0egwF7EqcOYWo8tK6lUp02M6YIj598sLAWy+EEcUfcO6OOkimgfFtSvblivIt/JltRk=,iv:/ZkWY20AyJQVWHkQpDurBx1SYrBUfCIa0AiBxG7D6tA=,tag:tPlR7QzCbEE2qeDPtWRmrw==,type:str] + lastmodified: "2026-07-04T20:18:11Z" + mac: ENC[AES256_GCM,data:ZQ1MZUGTXGyqLZ1DLy/pkVYugu0B4WUk8S9B/opv6RYtIHyjtBzOIj5r/slix5xX6f2hzk91DvjNy64UiYJAY550ZKanXu0kgp6nEwGl/KCY8porx/0TFbMQiMF/dYl17vBQFj6wvNldVzTER48Lqc/12gNf69Rn0JRBj4uAVm0=,iv:pjxwDK8O1lgf8oNRrZFvQXESdS04e6dEmNIMkNBye+0=,tag:8PmHx2UJ/ESBMJuIBuSQdw==,type:str] pgp: - created_at: "2026-06-02T01:50:38Z" enc: |- diff --git a/lib/starship.nix b/lib/starship.nix --- a/lib/starship.nix +++ b/lib/starship.nix @@ -1,6 +1,46 @@ {lib}: let - inherit (lib) genAttrs; + inherit (lib) genAttrs hasPrefix; in { + # Convert hex colors to starship-jj TrueColor values. + starshipJjTrueColor = value: let + hexDigits = { + "0" = 0; + "1" = 1; + "2" = 2; + "3" = 3; + "4" = 4; + "5" = 5; + "6" = 6; + "7" = 7; + "8" = 8; + "9" = 9; + A = 10; + B = 11; + C = 12; + D = 13; + E = 14; + F = 15; + a = 10; + b = 11; + c = 12; + d = 13; + e = 14; + f = 15; + }; + hex = + if hasPrefix "#" value + then builtins.substring 1 6 value + else value; + hexByte = offset: hexDigits.${builtins.substring offset 1 hex} * 16 + hexDigits.${builtins.substring (offset + 1) 1 hex}; + in { + TrueColor = { + r = hexByte 0; + g = hexByte 2; + b = hexByte 4; + }; + }; + + # keep-sorted start block=yes newline_separated=yes # Build a wrapped Starship segment that uses the shared `base01` background. starshipBase01Segment = body: fg: { format = "[ ](#00000000)[ ](bg:base01)[${body}]($style)[ ](bg:base01)"; @@ -12,4 +52,5 @@ # Disable Starship modules by name. starshipDisabledModules = modules: genAttrs modules (_: {disabled = true;}); + # keep-sorted end } diff --git a/.tangled/workflows/checks.yml b/.tangled/workflows/checks.yml --- a/.tangled/workflows/checks.yml +++ b/.tangled/workflows/checks.yml @@ -11,4 +11,4 @@ - name: "Run checks" command: | set -euo pipefail - nix flake check + nix --extra-experimental-features pipe-operators flake check diff --git a/modules/pkgs/default.nix b/modules/pkgs/default.nix --- a/modules/pkgs/default.nix +++ b/modules/pkgs/default.nix @@ -16,6 +16,7 @@ performant-mode pptx2md-adapter proton-port-forward + rclone-bisync-runner systemd-status-preview telescope-all-recent-nvim text-preview diff --git a/modules/pkgs/rclone-bisync-runner.nix b/modules/pkgs/rclone-bisync-runner.nix new file mode 100644 --- /dev/null +++ b/modules/pkgs/rclone-bisync-runner.nix @@ -0,0 +1,50 @@ +{ + perSystem = {pkgs, ...}: let + inherit (pkgs) writeShellApplication; + in { + packages.rclone-bisync-runner = writeShellApplication { + name = "rclone-bisync-runner"; + + runtimeInputs = with pkgs; [ + # keep-sorted start + coreutils + rclone + # keep-sorted end + ]; + + text = '' + set -eu + + : "''${LOCAL_PATH:?LOCAL_PATH is required}" + : "''${REMOTE_PATH:?REMOTE_PATH is required}" + : "''${WORK_DIR:?WORK_DIR is required}" + + initialized_marker="$WORK_DIR/initialized" + + mkdir -p "$LOCAL_PATH" "$WORK_DIR" + + rclone_args=( + bisync + "$LOCAL_PATH" + "$REMOTE_PATH" + --workdir "$WORK_DIR" + --create-empty-src-dirs + --resilient + --recover + --max-lock 2m + --conflict-resolve newer + --verbose + ) + + rclone mkdir "$REMOTE_PATH" + + if [ -e "$initialized_marker" ]; then + rclone "''${rclone_args[@]}" + else + rclone "''${rclone_args[@]}" --resync-mode newer + touch "$initialized_marker" + fi + ''; + }; + }; +} diff --git a/modules/profiles/base.nix b/modules/profiles/base.nix --- a/modules/profiles/base.nix +++ b/modules/profiles/base.nix @@ -70,10 +70,14 @@ # keep-sorted end # Programs + # keep-sorted start + delta git + jujutsu nur sops shellAbbreviations + # keep-sorted end # CLI # keep-sorted start diff --git a/modules/profiles/personal.nix b/modules/profiles/personal.nix --- a/modules/profiles/personal.nix +++ b/modules/profiles/personal.nix @@ -85,6 +85,7 @@ cpond diffnav direnv + gen-license gitfetch onefetch pipes @@ -117,13 +118,17 @@ seahorse showtime spotify + tangled zaread zathura zen # keep-sorted end # Services + # keep-sorted start flatpak + rclone + # keep-sorted end ]; }; } diff --git a/modules/programs/delta.nix b/modules/programs/delta.nix new file mode 100644 --- /dev/null +++ b/modules/programs/delta.nix @@ -0,0 +1,51 @@ +{ + flake.modules.homeManager.delta = { + # keep-sorted start + lib, + osConfig, + # keep-sorted end + ... + }: let + inherit (lib.self) blendHex; + + colors = osConfig.lib.stylix.colors.withHashtag; + in { + programs.delta = { + enable = true; + enableGitIntegration = true; + + options = with colors; { + true-color = "always"; + line-numbers = true; + side-by-side = true; + syntax-theme = "base16-stylix"; + + # Let the desktop MIME handler open linked files. + hyperlinks = true; + hyperlinks-file-link-format = "file://{path}#{line}"; + + # keep-sorted start + blame-palette = "${base00} ${base01} ${base02}"; + file-style = "${base0D} bold"; + hunk-header-decoration-style = "${base0D} ul"; + hunk-header-file-style = "${base0D} ul bold"; + hunk-header-line-number-style = "${base0A} box bold"; + line-numbers-left-style = base0D; + line-numbers-minus-style = base08; + line-numbers-plus-style = base0B; + line-numbers-right-style = base0D; + line-numbers-zero-style = base03; + merge-conflict-ours-diff-header-decoration-style = "${base0D} box"; + merge-conflict-ours-diff-header-style = "${base0A} bold"; + merge-conflict-theirs-diff-header-decoration-style = "${base0D} box"; + merge-conflict-theirs-diff-header-style = "${base0A} bold"; + minus-emph-style = "${base00} ${blendHex 34 base00 base08}"; + minus-style = "syntax ${blendHex 22 base00 base08}"; + plus-emph-style = "${base00} ${blendHex 34 base00 base0B}"; + plus-style = "syntax ${blendHex 22 base00 base0B}"; + whitespace-error-style = "${base00} bold"; + # keep-sorted end + }; + }; + }; +} diff --git a/modules/programs/git.nix b/modules/programs/git.nix --- a/modules/programs/git.nix +++ b/modules/programs/git.nix @@ -2,14 +2,11 @@ flake.modules.homeManager.git = { # keep-sorted start config, - lib, - osConfig, pkgs, vars, # keep-sorted end ... }: let - inherit (lib.self) blendHex; inherit (vars) # keep-sorted start @@ -19,8 +16,6 @@ username # keep-sorted end ; - - colors = osConfig.lib.stylix.colors.withHashtag; in { sops = { secrets = { @@ -43,14 +38,8 @@ home.file.".ssh/git.pub".text = gitPublicSshkey; programs = { - git = let - # Use git full so the libsecret credential helper is available. - gitPackage = pkgs.gitFull; - in { + git = { enable = true; - lfs.enable = true; - - package = gitPackage; settings = { user = { @@ -66,49 +55,11 @@ # keep-sorted end # Store https credentials via the desktop keyring (libsecret). - credential.helper = "${gitPackage}/libexec/git-core/git-credential-libsecret"; + credential.helper = "${pkgs.git}/libexec/git-core/git-credential-libsecret"; }; # Include the sops-generated snippet to set the email. includes = [{inherit (config.sops.templates."git-config") path;}]; - }; - - delta = { - enable = true; - enableGitIntegration = true; - - options = with colors; { - true-color = "always"; - line-numbers = true; - side-by-side = true; - syntax-theme = "base16-stylix"; - - # Let the desktop MIME handler open linked files. - hyperlinks = true; - hyperlinks-file-link-format = "file://{path}#{line}"; - - # keep-sorted start - blame-palette = "${base00} ${base01} ${base02}"; - file-style = "${base0D} bold"; - hunk-header-decoration-style = "${base0D} ul"; - hunk-header-file-style = "${base0D} ul bold"; - hunk-header-line-number-style = "${base0A} box bold"; - line-numbers-left-style = base0D; - line-numbers-minus-style = base08; - line-numbers-plus-style = base0B; - line-numbers-right-style = base0D; - line-numbers-zero-style = base03; - merge-conflict-ours-diff-header-decoration-style = "${base0D} box"; - merge-conflict-ours-diff-header-style = "${base0A} bold"; - merge-conflict-theirs-diff-header-decoration-style = "${base0D} box"; - merge-conflict-theirs-diff-header-style = "${base0A} bold"; - minus-emph-style = "${base00} ${blendHex 34 base00 base08}"; - minus-style = "syntax ${blendHex 22 base00 base08}"; - plus-emph-style = "${base00} ${blendHex 34 base00 base0B}"; - plus-style = "syntax ${blendHex 22 base00 base0B}"; - whitespace-error-style = "${base00} bold"; - # keep-sorted end - }; }; }; # keep-sorted end diff --git a/modules/programs/jujutsu.nix b/modules/programs/jujutsu.nix new file mode 100644 --- /dev/null +++ b/modules/programs/jujutsu.nix @@ -0,0 +1,53 @@ +{ + flake.modules.homeManager.jujutsu = { + # keep-sorted start + config, + vars, + # keep-sorted end + ... + }: let + inherit + (vars) + # keep-sorted start + fullName + gitPublicSshkey + gitSigningKey + username + # keep-sorted end + ; + in { + sops = { + secrets = { + # Per-user git email address stored in sops. + "git/email" = {}; + + # Place the decrypted private key at a stable path used by ssh. + "git/private_ssh_key".path = "/home/${username}/.ssh/git"; + }; + + templates."jj-user-config" = { + content = '' + [user] + email = "${config.sops.placeholder."git/email"}" + name = "${fullName}" + ''; + path = "${config.xdg.configHome}/jj/conf.d/10-user.toml"; + }; + }; + + # keep-sorted start block=yes newline_separated=yes + # Publish the public key alongside the private key path. + home.file.".ssh/git.pub".text = gitPublicSshkey; + + programs.jujutsu = { + enable = true; + + settings.signing = { + backend = "gpg"; + behavior = "own"; + key = gitSigningKey; + }; + }; + # keep-sorted end + }; +} diff --git a/modules/services/ananicy.nix b/modules/services/ananicy.nix deleted file mode 100644 --- a/modules/services/ananicy.nix +++ /dev/null @@ -1,10 +0,0 @@ -{ - flake.modules.nixos.ananicy = {pkgs, ...}: { - services.ananicy = { - enable = true; - package = pkgs.ananicy-cpp; - - rulesProvider = pkgs.ananicy-rules-cachyos; - }; - }; -} diff --git a/modules/services/apprise.nix b/modules/services/apprise.nix deleted file mode 100644 --- a/modules/services/apprise.nix +++ /dev/null @@ -1,50 +0,0 @@ -{ - flake.modules.nixos.apprise = { - virtualisation.oci-containers.containers.apprise = { - hostname = "apprise"; - image = "caronc/apprise:latest"; - - environment = { - APPRISE_ADMIN = "y"; - APPRISE_STATEFUL_MODE = "simple"; - APPRISE_WORKER_COUNT = "1"; - APPRISE_WORKER_MAX_REQUESTS = "200"; - }; - - extraOptions = [ - "--network=host" - "--tmpfs=/tmp" - "--user=1000:1000" - - # Health check. - "--health-cmd=curl -fsSo /dev/null http://127.0.0.1:8000/ || exit 1" - "--health-interval=60s" - "--health-retries=5" - "--health-start-period=30s" - "--health-timeout=5s" - ]; - - volumes = [ - # keep-sorted start - "/var/lib/apprise/attach:/attach" - "/var/lib/apprise/config:/config" - "/var/lib/apprise/plugin:/plugin" - # keep-sorted end - ]; - }; - - networking.firewall.extraInputRules = '' - # Allow containers to reach host Apprise API. - iifname "podman*" tcp dport 8000 accept - ''; - - systemd.tmpfiles.rules = [ - # keep-sorted start - "d /var/lib/apprise 0750 1000 1000 -" - "d /var/lib/apprise/attach 0750 1000 1000 -" - "d /var/lib/apprise/config 0750 1000 1000 -" - "d /var/lib/apprise/plugin 0750 1000 1000 -" - # keep-sorted end - ]; - }; -} diff --git a/modules/services/authentik.nix b/modules/services/authentik.nix deleted file mode 100644 --- a/modules/services/authentik.nix +++ /dev/null @@ -1,77 +0,0 @@ -{inputs, ...}: { - flake-file.inputs.authentik-nix = { - url = "github:nix-community/authentik-nix"; - inputs.flake-parts.follows = "flake-parts"; - }; - - flake.modules.nixos.authentik = { - # keep-sorted start - config, - vars, - # keep-sorted end - ... - }: let - inherit (vars) groundDomain; - in { - imports = [inputs.authentik-nix.nixosModules.default]; - - sops = { - secrets = { - # keep-sorted start - "authentik/proxy_token" = {}; - "authentik/secret_key" = {}; - # keep-sorted end - - # keep-sorted start - "authentik/email/from" = {}; - "authentik/email/host" = {}; - "authentik/email/password" = {}; - "authentik/email/username" = {}; - # keep-sorted end - }; - - templates = { - "authentik.env".content = '' - AUTHENTIK_SECRET_KEY=${config.sops.placeholder."authentik/secret_key"} - AUTHENTIK_EMAIL__HOST=${config.sops.placeholder."authentik/email/host"} - AUTHENTIK_EMAIL__USERNAME=${config.sops.placeholder."authentik/email/username"} - AUTHENTIK_EMAIL__PASSWORD=${config.sops.placeholder."authentik/email/password"} - AUTHENTIK_EMAIL__FROM=${config.sops.placeholder."authentik/email/from"} - ''; - - "authentik-proxy.env".content = '' - AUTHENTIK_HOST=https://authentik.${groundDomain} - AUTHENTIK_TOKEN=${config.sops.placeholder."authentik/proxy_token"} - ''; - }; - }; - - services = { - authentik = { - enable = true; - environmentFile = config.sops.templates."authentik.env".path; - - settings = { - avatars = "gravatar"; - - # Disable unrequired features. - disable_startup_analytics = true; - disable_update_check = true; - error_reporting.enabled = false; - - email = { - port = 465; - use_ssl = true; - }; - }; - }; - - authentik-proxy = { - enable = true; - environmentFile = config.sops.templates."authentik-proxy.env".path; - }; - }; - - systemd.services.authentik-worker.serviceConfig.TimeoutStopSec = "60s"; - }; -} diff --git a/modules/services/avahi.nix b/modules/services/avahi.nix deleted file mode 100644 --- a/modules/services/avahi.nix +++ /dev/null @@ -1,13 +0,0 @@ -{ - flake.modules.nixos.avahi = { - # mDNS/DNS-SD discovery for the local network. - services.avahi = { - enable = true; - - # keep-sorted start numeric=yes - nssmdns4 = true; - nssmdns6 = true; - # keep-sorted end - }; - }; -} diff --git a/modules/services/bluetooth.nix b/modules/services/bluetooth.nix deleted file mode 100644 --- a/modules/services/bluetooth.nix +++ /dev/null @@ -1,17 +0,0 @@ -{ - flake.modules.nixos.bluetooth = { - config = { - capabilities.bluetooth = true; - - hardware.bluetooth = { - enable = true; - - # Enable experimental features needed by some devices. - settings.General.Experimental = true; - - # Disable bluetooth power-on at boot to save battery. - powerOnBoot = false; - }; - }; - }; -} diff --git a/modules/services/bpftune.nix b/modules/services/bpftune.nix deleted file mode 100644 --- a/modules/services/bpftune.nix +++ /dev/null @@ -1,5 +0,0 @@ -{ - flake.modules.nixos.bpftune = { - services.bpftune.enable = true; - }; -} diff --git a/modules/services/btrfs-autoscrub.nix b/modules/services/btrfs-autoscrub.nix deleted file mode 100644 --- a/modules/services/btrfs-autoscrub.nix +++ /dev/null @@ -1,7 +0,0 @@ -{ - flake.modules.nixos.btrfs-autoscrub = { - services.btrfs.autoScrub = { - enable = true; - }; - }; -} diff --git a/modules/services/cloudbeaver.nix b/modules/services/cloudbeaver.nix deleted file mode 100644 --- a/modules/services/cloudbeaver.nix +++ /dev/null @@ -1,115 +0,0 @@ -{ - flake.modules.nixos.cloudbeaver = { - # keep-sorted start - config, - lib, - pkgs, - vars, - # keep-sorted end - ... - }: let - inherit (lib) mkForce; - - secrets = config.sops.secrets; - templates = config.sops.templates; - - inherit (vars) groundDomain; - in { - sops = { - secrets."cloudbeaver/database_password" = {}; - - templates."cloudbeaver.env".content = '' - CLOUDBEAVER_DB_PASSWORD=${config.sops.placeholder."cloudbeaver/database_password"} - CLOUDBEAVER_QM_DB_PASSWORD=${config.sops.placeholder."cloudbeaver/database_password"} - ''; - }; - - virtualisation.oci-containers.containers.cloudbeaver = { - hostname = "cloudbeaver"; - image = "dbeaver/cloudbeaver:latest"; - - extraOptions = [ - "--network=host" - - # Health check. - "--health-cmd=curl -fsSo /dev/null http://127.0.0.1:8978/ || exit 1" - "--health-interval=60s" - "--health-retries=5" - "--health-start-period=30s" - "--health-timeout=5s" - ]; - - environment = { - CB_SERVER_URL = "https://cloudbeaver.${groundDomain}"; - CLOUDBEAVER_APP_FORWARD_PROXY = "true"; - CLOUDBEAVER_DB_BACKUP_ENABLED = "false"; - CLOUDBEAVER_DB_DRIVER = "postgres-jdbc"; - CLOUDBEAVER_DB_SCHEMA = "public"; - CLOUDBEAVER_DB_URL = "jdbc:postgresql://127.0.0.1:5432/cloudbeaver"; - CLOUDBEAVER_DB_USER = "cloudbeaver"; - CLOUDBEAVER_QM_DB_BACKUP_ENABLED = "false"; - CLOUDBEAVER_QM_DB_DRIVER = "postgres-jdbc"; - CLOUDBEAVER_QM_DB_SCHEMA = "public"; - CLOUDBEAVER_QM_DB_URL = "jdbc:postgresql://127.0.0.1:5432/cloudbeaver"; - CLOUDBEAVER_QM_DB_USER = "cloudbeaver"; - }; - - environmentFiles = [templates."cloudbeaver.env".path]; - volumes = ["/var/lib/cloudbeaver:/opt/cloudbeaver/workspace"]; - }; - - systemd = { - tmpfiles.rules = ["d /var/lib/cloudbeaver 0750 8978 8978 -"]; - - services = { - cloudbeaver-postgres-password = { - # keep-sorted start block=yes newline_separated=yes - after = [ - "postgresql.service" - "sops-install-secrets.service" - ]; - - before = ["podman-cloudbeaver.service"]; - - wantedBy = ["podman-cloudbeaver.service"]; - - wants = [ - "postgresql.service" - "sops-install-secrets.service" - ]; - # keep-sorted end - - script = '' - set -eu - password="$(${pkgs.coreutils}/bin/cat "$CREDENTIALS_DIRECTORY/database_password")" - ${config.services.postgresql.package}/bin/psql --dbname postgres --command "ALTER USER cloudbeaver WITH PASSWORD \$cloudbeaver\$''${password}\$cloudbeaver\$;" - ''; - - serviceConfig = { - # keep-sorted start - Group = "postgres"; - Type = "oneshot"; - User = "postgres"; - # keep-sorted end - - # keep-sorted start - LoadCredential = ["database_password:${secrets."cloudbeaver/database_password".path}"]; - RemainAfterExit = true; - # keep-sorted end - }; - }; - - podman-cloudbeaver = { - after = ["cloudbeaver-postgres-password.service"]; - stopIfChanged = false; - wants = ["cloudbeaver-postgres-password.service"]; - - serviceConfig = { - SuccessExitStatus = [143]; - TimeoutStopSec = mkForce "60s"; - }; - }; - }; - }; - }; -} diff --git a/modules/services/crowdsec.nix b/modules/services/crowdsec.nix deleted file mode 100644 --- a/modules/services/crowdsec.nix +++ /dev/null @@ -1,386 +0,0 @@ -{inputs, ...}: { - flake-file = { - inputs.nixpkgs-crowdsec = { - url = "github:TornaxO7/nixpkgs/crowdsec"; - }; - - inputs.nixpkgs-crowdsec-blocklist-import = { - url = "github:gaelj/nixpkgs/init-crowdsec-blocklist-import"; - }; - }; - - flake.overlays.crowdsec = final: _prev: let - inherit (final.stdenv.hostPlatform) system; - crowdsecPkgs = inputs.nixpkgs-crowdsec.legacyPackages.${system}; - in { - inherit - (crowdsecPkgs) - # keep-sorted start - crowdsec - crowdsec-firewall-bouncer - # keep-sorted end - ; - }; - - flake.modules.nixos.crowdsec-base = { - # keep-sorted start - config, - self, - # keep-sorted end - ... - }: { - nixpkgs.overlays = [self.overlays.crowdsec]; - - disabledModules = [ - # keep-sorted start - "services/security/crowdsec-firewall-bouncer.nix" - "services/security/crowdsec.nix" - # keep-sorted end - ]; - - imports = [ - # keep-sorted start - "${inputs.nixpkgs-crowdsec-blocklist-import}/nixos/modules/services/security/crowdsec-blocklist-import.nix" - "${inputs.nixpkgs-crowdsec}/nixos/modules/services/security/crowdsec-firewall-bouncer.nix" - "${inputs.nixpkgs-crowdsec}/nixos/modules/services/security/crowdsec.nix" - # keep-sorted end - ]; - - services.crowdsec = { - enable = true; - autoUpdateService = true; - openFirewall = false; - - hub = { - collections = [ - # keep-sorted start - "LePresidente/jellyfin" - "LePresidente/jellyseerr" - "baudneo/gotify" - "crowdsecurity/appsec-generic-rules" - "crowdsecurity/appsec-virtual-patching" - "crowdsecurity/http-cve" - "crowdsecurity/linux" - "crowdsecurity/sshd" - "crowdsecurity/traefik" - "crowdsecurity/whitelist-good-actors" - "firix/authentik" - # keep-sorted end - ]; - }; - - settings = { - acquisitions = [ - { - journalctl_filter = ["_SYSTEMD_UNIT=sshd.service"]; - labels.type = "syslog"; - source = "journalctl"; - } - ]; - }; - }; - - users = { - groups.${config.services.crowdsec.group} = {}; - - users.${config.services.crowdsec.user} = { - group = config.services.crowdsec.group; - isSystemUser = true; - }; - }; - }; - - flake.modules.nixos.crowdsec-agent = {self, ...}: { - imports = [self.modules.nixos.crowdsec-base]; - - services.crowdsec.settings.config.api.server.enable = false; - }; - - flake.modules.nixos.crowdsec-server = { - # keep-sorted start - config, - lib, - pkgs, - self, - # keep-sorted end - ... - }: let - inherit - (lib) - # keep-sorted start - getExe - mkForce - # keep-sorted end - ; - - secrets = config.sops.secrets; - templates = config.sops.templates; - - dataDir = "/var/lib/crowdsec/data"; - gotifyUrl = "http://127.0.0.1:44407/message"; - - proxyPort = "12346"; - - setupDeps = [ - "postgresql.service" - "sops-install-secrets.service" - ]; - - setupUnit = { - after = setupDeps; - wants = setupDeps; - }; - in { - imports = [self.modules.nixos.crowdsec-base]; - - sops = { - secrets = { - # keep-sorted start - "crowdsec/console_enroll_key" = {}; - "crowdsec/gotify_api_key" = {}; - "traefik/crowdsec_bouncer_key" = {}; - # keep-sorted end - }; - - templates = { - "crowdsec-blocklist-import-env" = { - mode = "0640"; - owner = config.services.crowdsec.user; - group = config.services.crowdsec.group; - path = "/etc/crowdsec/blocklist-import.env"; - content = '' - WEBHOOK_TYPE: "generic" - WEBHOOK_URL=http://127.0.0.1:${proxyPort} - ''; - }; - - "crowdsec-gotify-notification" = { - mode = "0640"; - owner = config.services.crowdsec.user; - group = config.services.crowdsec.group; - path = "/etc/crowdsec/notifications/gotify-alerts.yaml"; - content = '' - type: http - name: gotify - log_level: info - group_threshold: 1 - url: ${gotifyUrl} - method: POST - headers: - X-Gotify-Key: ${config.sops.placeholder."crowdsec/gotify_api_key"} - Content-Type: application/json - format: | - {{ range . -}} - {{ $alert := . -}} - {{ $scenario := $alert.GetScenario -}} - {{ $source := $alert.GetValue -}} - { - "extras": { - "client::display": { - "contentType": "text/markdown" - } - }, - "priority": 3, - "title": "CrowdSec Alert", - "message": {{ printf "**Scenario:** `%s`\n\n**IP:** `%s`\n\n**Machine:** `%s`" $scenario $source $alert.MachineID | toJson }} - } - {{ end -}} - ''; - }; - }; - }; - - services = { - crowdsec.settings = { - config = { - api.server.online_client.credentials_path = "${dataDir}/online_api_credentials.yaml"; - - db_config = { - db_name = "crowdsec"; - db_path = "/run/postgresql"; - type = "pgx"; - user = "crowdsec"; - }; - }; - - console.enrollKeyFile = secrets."crowdsec/console_enroll_key".path; - - profiles = [ - { - filters = [''Alert.GetScenario() != "" && !(Alert.GetScenario() contains "external/blocklist")'']; - name = "all_scenario_notifications"; - notifications = ["gotify"]; - on_success = "continue"; - } - - { - decisions = [ - { - duration = "4h"; - type = "ban"; - } - ]; - - filters = [''Alert.Remediation == true && Alert.GetScope() == "Ip"'']; - name = "default_ip_remediation"; - on_success = "break"; - } - - { - decisions = [ - { - duration = "4h"; - type = "ban"; - } - ]; - - filters = [''Alert.Remediation == true && Alert.GetScope() == "Range"'']; - name = "default_ip_remediation"; - on_success = "break"; - } - ]; - }; - - crowdsec-firewall-bouncer = { - enable = true; - registerBouncer.enable = true; - createRulesets = true; - }; - - crowdsec-blocklist-import = { - enable = true; - allowListGithub = true; - }; - }; - - systemd.services = { - crowdsec = setupUnit; - crowdsec-setup = setupUnit; - - crowdsec-blocklist-import-frequent = { - after = [ - # keep-sorted start - "crowdsec-blocklist-gotify-proxy.service" - "crowdsec-firewall-bouncer-register.service" - "sops-install-secrets.service" - # keep-sorted end - ]; - wants = [ - # keep-sorted start - "crowdsec-blocklist-gotify-proxy.service" - "crowdsec-firewall-bouncer-register.service" - "sops-install-secrets.service" - # keep-sorted end - ]; - - serviceConfig.EnvironmentFile = templates."crowdsec-blocklist-import-env".path; - }; - - crowdsec-blocklist-import-limited = { - after = [ - # keep-sorted start - "crowdsec-blocklist-gotify-proxy.service" - "crowdsec-firewall-bouncer-register.service" - "sops-install-secrets.service" - # keep-sorted end - ]; - wants = [ - # keep-sorted start - "crowdsec-blocklist-gotify-proxy.service" - "crowdsec-firewall-bouncer-register.service" - "sops-install-secrets.service" - # keep-sorted end - ]; - - serviceConfig.EnvironmentFile = templates."crowdsec-blocklist-import-env".path; - }; - - crowdsec-blocklist-gotify-proxy = { - description = "Transform blocklist-import webhook payload for Gotify"; - - after = ["sops-install-secrets.service"]; - stopIfChanged = false; - wants = ["sops-install-secrets.service"]; - - wantedBy = ["crowdsec-blocklist-import-frequent.service" "crowdsec-blocklist-import-limited.service"]; - - serviceConfig = { - # keep-sorted start - DynamicUser = true; - ExecStart = "${getExe pkgs.socat} TCP-LISTEN:${proxyPort},bind=127.0.0.1,fork,reuseaddr SYSTEM:${getExe pkgs.crowdsec-blocklist-gotify-proxy}"; - LoadCredential = ["gotify_api_key:${secrets."crowdsec/gotify_api_key".path}"]; - Restart = "on-failure"; - StateDirectory = "crowdsec"; - StateDirectoryMode = "0750"; - SuccessExitStatus = [143]; - Type = "simple"; - # keep-sorted end - }; - }; - - # PostgreSQL local socket access. - crowdsec-firewall-bouncer-register.serviceConfig.RestrictAddressFamilies = mkForce ["AF_UNIX"]; - - crowdsec-traefik-bouncer = { - description = "Register Traefik CrowdSec bouncer"; - - # keep-sorted start block=yes newline_separated=yes - after = [ - # keep-sorted start - "crowdsec.service" - "sops-install-secrets.service" - # keep-sorted end - ]; - - before = ["traefik.service"]; - - wantedBy = ["traefik.service"]; - - wants = [ - # keep-sorted start - "crowdsec.service" - "sops-install-secrets.service" - # keep-sorted end - ]; - # keep-sorted end - - script = '' - set -eu - - attempt=1 - while [ "$attempt" -le 30 ]; do - if ${pkgs.crowdsec}/bin/cscli bouncers list | ${pkgs.gnugrep}/bin/grep -q "traefik-bouncer"; then - exit 0 - fi - - if ${pkgs.crowdsec}/bin/cscli bouncers add "traefik-bouncer" --key "$(${pkgs.coreutils}/bin/cat "$CREDENTIALS_DIRECTORY/traefik_bouncer_key")" >/dev/null; then - exit 0 - fi - - attempt=$((attempt + 1)) - ${pkgs.coreutils}/bin/sleep 2 - done - - ${pkgs.crowdsec}/bin/cscli bouncers list - exit 1 - ''; - - serviceConfig = { - # keep-sorted start - DynamicUser = true; - Group = config.services.crowdsec.group; - StateDirectory = "crowdsec"; - StateDirectoryMode = "0750"; - User = config.services.crowdsec.user; - # keep-sorted end - - # keep-sorted start - LoadCredential = ["traefik_bouncer_key:${secrets."traefik/crowdsec_bouncer_key".path}"]; - RemainAfterExit = true; - Type = "oneshot"; - # keep-sorted end - }; - }; - }; - }; -} diff --git a/modules/services/dockhand.nix b/modules/services/dockhand.nix deleted file mode 100644 --- a/modules/services/dockhand.nix +++ /dev/null @@ -1,98 +0,0 @@ -{ - flake.modules.nixos.dockhand = { - # keep-sorted start - config, - lib, - pkgs, - # keep-sorted end - ... - }: let - inherit (lib) mkForce; - - secrets = config.sops.secrets; - templates = config.sops.templates; - in { - sops = { - secrets."dockhand/database_password" = {}; - - templates."dockhand.env".content = '' - DATABASE_URL=postgres://dockhand:${config.sops.placeholder."dockhand/database_password"}@127.0.0.1:5432/dockhand - ''; - }; - - virtualisation.oci-containers.containers.dockhand = { - hostname = "dockhand"; - image = "fnsys/dockhand:latest"; - - extraOptions = [ - "--network=host" - - # Health check. - "--health-cmd=curl -fsSo /dev/null http://127.0.0.1:3000/api/health || exit 1" - "--health-interval=60s" - "--health-retries=5" - "--health-start-period=30s" - "--health-timeout=5s" - ]; - - environmentFiles = [templates."dockhand.env".path]; - volumes = ["/var/lib/dockhand:/app/data"]; - }; - - networking.firewall.interfaces.wg0.allowedTCPPorts = [3000]; - - systemd = { - tmpfiles.rules = ["d /var/lib/dockhand 0750 1001 1001 -"]; - - services = { - dockhand-postgres-password = { - # keep-sorted start block=yes newline_separated=yes - after = [ - "postgresql.service" - "sops-install-secrets.service" - ]; - - before = ["podman-dockhand.service"]; - - wantedBy = ["podman-dockhand.service"]; - - wants = [ - "postgresql.service" - "sops-install-secrets.service" - ]; - # keep-sorted end - - script = '' - set -eu - password="$(${pkgs.coreutils}/bin/cat "$CREDENTIALS_DIRECTORY/database_password")" - ${config.services.postgresql.package}/bin/psql --dbname postgres --command "ALTER USER dockhand WITH PASSWORD \$dockhand\$''${password}\$dockhand\$;" - ''; - - serviceConfig = { - # keep-sorted start - Group = "postgres"; - Type = "oneshot"; - User = "postgres"; - # keep-sorted end - - # keep-sorted start - LoadCredential = ["database_password:${secrets."dockhand/database_password".path}"]; - RemainAfterExit = true; - # keep-sorted end - }; - }; - - podman-dockhand = { - after = ["dockhand-postgres-password.service"]; - stopIfChanged = false; - wants = ["dockhand-postgres-password.service"]; - - serviceConfig = { - SuccessExitStatus = [143]; - TimeoutStopSec = mkForce "60s"; - }; - }; - }; - }; - }; -} diff --git a/modules/services/envfs.nix b/modules/services/envfs.nix deleted file mode 100644 --- a/modules/services/envfs.nix +++ /dev/null @@ -1,27 +0,0 @@ -{self, ...}: { - flake.overlays.envfs = _final: prev: let - src = prev.fetchFromGitHub { - owner = "Mic92"; - repo = "envfs"; - rev = "1.2.0"; - hash = "sha256-hj/6zS9ebF0IDqgc1Dne59nWx80nk6jn2gj8BzQUFIQ="; - }; - in { - # Pin envfs until nixpkgs picks up the upstream mount helper fix. - envfs = prev.envfs.overrideAttrs (_oldAttrs: { - version = "1.2.0"; - inherit src; - cargoDeps = prev.rustPlatform.fetchCargoVendor { - inherit src; - hash = "sha256-dz3gpE464jnmSDsAsmJHcxUsEKeUURNoUjgGU2214Xg="; - }; - }); - }; - - flake.modules.nixos.envfs = { - nixpkgs.overlays = [self.overlays.envfs]; - - # Provide fhs-style paths for compatibility with legacy applications. - services.envfs.enable = true; - }; -} diff --git a/modules/services/evolution-data-server.nix b/modules/services/evolution-data-server.nix deleted file mode 100644 --- a/modules/services/evolution-data-server.nix +++ /dev/null @@ -1,5 +0,0 @@ -{ - flake.modules.nixos.evolution-data-server = { - services.gnome.evolution-data-server.enable = true; - }; -} diff --git a/modules/services/firewall.nix b/modules/services/firewall.nix deleted file mode 100644 --- a/modules/services/firewall.nix +++ /dev/null @@ -1,5 +0,0 @@ -{ - flake.modules.nixos.firewall = { - networking.nftables.enable = true; - }; -} diff --git a/modules/services/flaresolverr.nix b/modules/services/flaresolverr.nix deleted file mode 100644 --- a/modules/services/flaresolverr.nix +++ /dev/null @@ -1,9 +0,0 @@ -{ - flake.modules.nixos.flaresolverr = { - services.flaresolverr.enable = true; - - networking.firewall.extraInputRules = '' - iifname "podman*" tcp dport 8191 accept - ''; - }; -} diff --git a/modules/services/flatpak.nix b/modules/services/flatpak.nix deleted file mode 100644 --- a/modules/services/flatpak.nix +++ /dev/null @@ -1,42 +0,0 @@ -{inputs, ...}: { - flake-file.inputs.nix-flatpak.url = "github:gmodena/nix-flatpak"; - - flake.modules.nixos.flatpak = { - services.flatpak.enable = true; - }; - - flake.modules.homeManager.flatpak = {config, ...}: let - home = config.home.homeDirectory; - in { - imports = [inputs.nix-flatpak.homeManagerModules.nix-flatpak]; - - services.flatpak.overrides.global = { - # keep-sorted start block=yes newline_separated=yes - # Add icons and dconf to the extra files. - Context.filesystems = [ - # keep-sorted start - "${home}/.icons" - "xdg-config/dconf:ro" - # keep-sorted end - ]; - - # Export gtk, gdk, and qt variables. - Environment = { - # keep-sorted start - GDK_BACKEND = "wayland,x11,*"; - GDK_DEBUG = "portals"; - GDK_SCALE = "1"; - GSK_RENDERER = "vulkan"; - GTK_USE_PORTAL = "1"; - # keep-sorted end - - # keep-sorted start - QT_AUTO_SCREEN_SCALE_FACTOR = "1"; - QT_QPA_PLATFORM = "wayland;xcb"; - QT_WAYLAND_DISABLE_WINDOWDECORATION = "1"; - # keep-sorted end - }; - # keep-sorted end - }; - }; -} diff --git a/modules/services/geoclue.nix b/modules/services/geoclue.nix deleted file mode 100644 --- a/modules/services/geoclue.nix +++ /dev/null @@ -1,20 +0,0 @@ -{ - flake.modules.nixos.geoclue = {config, ...}: let - cfgWifi = config.capabilities.wifi; - in { - services.geoclue2 = { - enable = true; - - # Use wi-fi positioning only when wi-fi support is enabled for the host. - enableWifi = cfgWifi; - - # Disable radio/serial backends to avoid unnecessary hardware usage. - # keep-sorted start - enable3G = false; - enableCDMA = false; - enableModemGPS = false; - enableNmea = false; - # keep-sorted end - }; - }; -} diff --git a/modules/services/gnome-keyring.nix b/modules/services/gnome-keyring.nix deleted file mode 100644 --- a/modules/services/gnome-keyring.nix +++ /dev/null @@ -1,20 +0,0 @@ -{ - flake.modules.nixos.gnome-keyring = { - # keep-sorted start block=yes newline_separated=yes - security.pam.services = { - greetd.enableGnomeKeyring = true; - login.enableGnomeKeyring = true; - }; - - services.gnome = { - # keep-sorted start newline_separated=yes - # Disable the gcr SSH agent managed by GNOME. - gcr-ssh-agent.enable = false; - - # Start keyring services for secret storage. - gnome-keyring.enable = true; - # keep-sorted end - }; - # keep-sorted end - }; -} diff --git a/modules/services/godns.nix b/modules/services/godns.nix deleted file mode 100644 --- a/modules/services/godns.nix +++ /dev/null @@ -1,129 +0,0 @@ -_: { - flake.overlays.godns = _final: prev: let - version = "3.4.1"; - - src = prev.fetchFromGitHub { - owner = "TimothyYe"; - repo = "godns"; - tag = "v${version}"; - hash = "sha256-LdMeb7pFYj+6HdUBgFkS756oox2HRgkwlHz65SgJoqY="; - }; - - packageLock = prev.fetchurl { - url = "https://raw.githubusercontent.com/tbutter/nixpkgs/a761abce85346f7de12fc7f2e792e6c7230f5217/pkgs/by-name/go/godns/package-lock.json"; - hash = "sha256-Lp3M2Ql4+Mr3qRdAqFAIE54BUwEIJWlsKiArZT41TXA="; - }; - in { - # Pin until NixOS/nixpkgs#518713 is merged. - godns = prev.godns.overrideAttrs (oldAttrs: { - inherit packageLock src version; - - ldflags = [ - "-s" - "-w" - "-X main.Version=${version}" - "-buildid=" - ]; - - npmDeps = prev.fetchNpmDeps { - src = prev.stdenv.mkDerivation { - name = "godns-web-src"; - inherit packageLock; - src = "${src}/web"; - - dontUnpack = true; - installPhase = '' - mkdir $out - cp -r $src/* $out - chmod +w $out - cp $packageLock $out/package-lock.json - ''; - }; - hash = "sha256-f8BU3HfQX9E+AFpXvNjRJNwT5nX1WwyinMRb7DP0FYU="; - }; - - postPatch = - (oldAttrs.postPatch or "") - + '' - cp ${packageLock} web/package-lock.json - substituteInPlace internal/provider/porkbun/porkbun_handler.go \ - --replace-fail 'ID string `json:"id,omitempty"`' 'ID interface{} `json:"id,omitempty"`' - ''; - - __darwinAllowLocalNetworking = true; - }); - }; - - flake.modules.nixos.godns = { - # keep-sorted start - config, - pkgs, - self, - vars, - # keep-sorted end - ... - }: let - inherit - (vars) - # keep-sorted start - groundDomain - orbitDomain - # keep-sorted end - ; - - secrets = config.sops.secrets; - in { - nixpkgs.overlays = [self.overlays.godns]; - - sops.secrets = { - # keep-sorted start - "godns/login_token" = {}; - "godns/password" = {}; - # keep-sorted end - }; - - services.godns = { - enable = true; - - settings = { - provider = "Porkbun"; - login_token_file = "$CREDENTIALS_DIRECTORY/login_token"; - password_file = "$CREDENTIALS_DIRECTORY/password"; - - domains = let - mkDomain = domain: { - domain_name = domain; - sub_domains = ["@" "*"]; - }; - in [ - # keep-sorted start - (mkDomain groundDomain) - (mkDomain orbitDomain) - # keep-sorted end - ]; - - resolver = "8.8.8.8"; - ip_type = "IPv4"; - interval = 300; - - ip_urls = [ - # keep-sorted start - "https://api-ipv4.ip.sb/ip" - "https://api.ip.sb/ip" - "https://api.ipify.org" - "https://myip.biturl.top" - # keep-sorted end - ]; - }; - - loadCredential = [ - # keep-sorted start - "login_token:${secrets."godns/login_token".path}" - "password:${secrets."godns/password".path}" - # keep-sorted end - ]; - }; - - systemd.services.godns.environment.SSL_CERT_FILE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"; - }; -} diff --git a/modules/services/gotify.nix b/modules/services/gotify.nix deleted file mode 100644 --- a/modules/services/gotify.nix +++ /dev/null @@ -1,133 +0,0 @@ -{ - flake.modules.nixos.gotify-server = { - # keep-sorted start - config, - lib, - pkgs, - vars, - # keep-sorted end - ... - }: let - inherit (lib) mkForce; - - inherit (pkgs.nur.repos.adam0) gotifyPlugins; - - gotifyPluginsDrv = pkgs.symlinkJoin { - name = "gotify-plugins"; - paths = [gotifyPlugins.authentik]; - }; - - templates = config.sops.templates; - inherit (vars) groundDomain; - in { - sops = { - secrets = { - "gotify/client_id" = {}; - "gotify/client_secret" = {}; - }; - - templates."gotify.env".content = '' - GOTIFY_OIDC_CLIENTID=${config.sops.placeholder."gotify/client_id"} - GOTIFY_OIDC_CLIENTSECRET=${config.sops.placeholder."gotify/client_secret"} - ''; - }; - - services.gotify = { - enable = true; - package = pkgs.nur.repos.adam0.gotify-server; - stateDirectoryName = "gotify"; - - environment = { - GOTIFY_SERVER_PORT = 44407; - GOTIFY_SERVER_SECURECOOKIE = "true"; - - GOTIFY_OIDC_ENABLED = "true"; - GOTIFY_OIDC_ISSUER = "https://authentik.${groundDomain}/application/o/gotify/"; - GOTIFY_OIDC_LINK_BY_USERNAME = "true"; - GOTIFY_OIDC_REDIRECTURL = "https://gotify.${groundDomain}/auth/oidc/callback"; - - GOTIFY_DATABASE_DIALECT = "postgres"; - GOTIFY_DATABASE_CONNECTION = "host=/run/postgresql user=gotify dbname=gotify sslmode=disable"; - }; - - environmentFiles = [templates."gotify.env".path]; - }; - - users = { - groups.gotify = {}; - - users.gotify = { - group = "gotify"; - isSystemUser = true; - }; - }; - - networking.firewall.extraInputRules = '' - # Allow containers to reach host Gotify. - iifname "podman*" tcp dport 44407 accept - ''; - - systemd.services.gotify-server = { - after = [ - # keep-sorted start - "authentik-worker.service" - "authentik.service" - "postgresql.service" - "sops-install-secrets.service" - "systemd-tmpfiles-setup.service" - "traefik.service" - # keep-sorted end - ]; - - wants = [ - # keep-sorted start - "authentik-worker.service" - "authentik.service" - "postgresql.service" - "sops-install-secrets.service" - "systemd-tmpfiles-setup.service" - "traefik.service" - # keep-sorted end - ]; - - unitConfig = { - StartLimitBurst = 60; - StartLimitIntervalSec = "5min"; - }; - - serviceConfig = { - DynamicUser = mkForce false; - User = "gotify"; - Group = "gotify"; - RestartSec = "5s"; - }; - }; - - systemd.tmpfiles.rules = ["L+ /var/lib/gotify/data/plugins - - - - ${gotifyPluginsDrv}"]; - - systemd.services.gotify-optimize-images = { - description = "Optimize Gotify uploaded images"; - - after = ["gotify-server.service"]; - requires = ["gotify-server.service"]; - - serviceConfig = { - Type = "oneshot"; - User = "gotify"; - Group = "gotify"; - ExecStart = "${pkgs.gotify-optimize-images}/bin/gotify-optimize-images"; - }; - }; - - systemd.timers.gotify-optimize-images = { - description = "Daily Gotify image optimization"; - - wantedBy = ["timers.target"]; - - timerConfig = { - OnCalendar = "daily"; - Persistent = true; - }; - }; - }; -} diff --git a/modules/services/gvfs.nix b/modules/services/gvfs.nix deleted file mode 100644 --- a/modules/services/gvfs.nix +++ /dev/null @@ -1,11 +0,0 @@ -{ - flake.modules.nixos.gvfs = {pkgs, ...}: { - # GIO/GVFS backends: trash, SMB/MTP/AFC, network mounts, and more. - services.gvfs = { - enable = true; - - # Use minimal gvfs without the full GNOME desktop. - package = pkgs.gvfs; - }; - }; -} diff --git a/modules/services/hawser.nix b/modules/services/hawser.nix deleted file mode 100644 --- a/modules/services/hawser.nix +++ /dev/null @@ -1,69 +0,0 @@ -{ - flake.modules.nixos.hawser = { - # keep-sorted start - config, - lib, - # keep-sorted end - ... - }: let - inherit - (lib) - # keep-sorted start - mkForce - mkOption - types - # keep-sorted end - ; - - hostname = config.networking.hostName; - hawserTokenSecret = "dockhand/hawser_tokens/${hostname}"; - in { - options.services.hawser.dockhandServerUrl = mkOption { - type = types.str; - default = "ws://10.100.0.1:3000/api/hawser/connect"; - description = "WebSocket URL for the dockhand server agent connection endpoint."; - }; - - config = { - sops.secrets.${hawserTokenSecret} = {}; - - sops.templates."hawser.env".content = '' - TOKEN=${config.sops.placeholder.${hawserTokenSecret}} - ''; - - virtualisation.oci-containers.containers.hawser = { - hostname = "hawser"; - image = "ghcr.io/finsys/hawser:latest"; - - extraOptions = [ - "--cgroupns=host" - "--network=host" - "--pid=host" - - # Health check. - "--health-cmd=wget -q --spider http://[::1]:2376/_hawser/health || exit 1" - "--health-interval=60s" - "--health-retries=5" - "--health-start-period=30s" - "--health-timeout=5s" - ]; - - environment = { - AGENT_NAME = hostname; - DOCKHAND_SERVER_URL = config.services.hawser.dockhandServerUrl; - }; - - environmentFiles = [config.sops.templates."hawser.env".path]; - volumes = [ - "/run/podman/podman.sock:/var/run/docker.sock" - "/var/lib/hawser:/data/stacks" - ]; - }; - - systemd = { - tmpfiles.rules = ["d /var/lib/hawser 0750 root root -"]; - services.podman-hawser.serviceConfig.TimeoutStopSec = mkForce "60s"; - }; - }; - }; -} diff --git a/modules/services/libinput.nix b/modules/services/libinput.nix deleted file mode 100644 --- a/modules/services/libinput.nix +++ /dev/null @@ -1,20 +0,0 @@ -{ - flake.modules.nixos.libinput = { - services.libinput.enable = true; - }; - - flake.modules.nixos.roccat = { - environment.etc."libinput/local-overrides.quirks" = let - name = "ROCCAT ROCCAT Kain 100"; - in { - text = '' - [${name}] - MatchName=${name} - ModelBouncingKeys=1 - ''; - mode = "0644"; - user = "root"; - group = "root"; - }; - }; -} diff --git a/modules/services/locate.nix b/modules/services/locate.nix deleted file mode 100644 --- a/modules/services/locate.nix +++ /dev/null @@ -1,9 +0,0 @@ -{ - flake.modules.nixos.locate = { - services.locate = { - enable = true; - - interval = "daily"; - }; - }; -} diff --git a/modules/services/mysql.nix b/modules/services/mysql.nix deleted file mode 100644 --- a/modules/services/mysql.nix +++ /dev/null @@ -1,90 +0,0 @@ -{ - flake.modules.nixos.mysql = { - # keep-sorted start - config, - pkgs, - vars, - # keep-sorted end - ... - }: let - inherit (vars) username; - in { - sops.secrets."mysql/admin_password" = {}; - - services.mysql = { - enable = true; - package = pkgs.mariadb; - - settings = { - mysqld = { - # Firewall limits remote access to WireGuard. - bind-address = "0.0.0.0"; - innodb_buffer_pool_size = "512M"; - max_connections = 100; - slow_query_log = true; - long_query_time = 1; - }; - - mysqldump.max_allowed_packet = "64M"; - }; - }; - - services.mysqlBackup = { - enable = true; - calendar = "02:00:00"; - - compressionAlg = "zstd"; - compressionLevel = 3; - }; - - networking.firewall = { - interfaces.wg0.allowedTCPPorts = [3306]; - - extraInputRules = '' - # Allow containers to reach host MariaDB. - iifname "podman*" tcp dport 3306 accept - ''; - }; - - systemd.services.mysql-admin = { - # keep-sorted start block=yes newline_separated=yes - after = [ - "mysql.service" - "sops-install-secrets.service" - ]; - - wantedBy = ["multi-user.target"]; - - wants = [ - "mysql.service" - "sops-install-secrets.service" - ]; - # keep-sorted end - - script = '' - set -eu - password="$(${pkgs.coreutils}/bin/tr -d '\r\n' < "$CREDENTIALS_DIRECTORY/admin_password")" - escaped_password="''${password//\'/\'\'}" - ${config.services.mysql.package}/bin/mysql -e " - CREATE USER IF NOT EXISTS '${username}'@'127.0.0.1' IDENTIFIED BY '$escaped_password'; - ALTER USER '${username}'@'127.0.0.1' IDENTIFIED BY '$escaped_password'; - GRANT ALL PRIVILEGES ON *.* TO '${username}'@'127.0.0.1' WITH GRANT OPTION; - FLUSH PRIVILEGES; - " - ''; - - serviceConfig = { - # keep-sorted start - Group = "root"; - Type = "oneshot"; - User = "root"; - # keep-sorted end - - # keep-sorted start - LoadCredential = ["admin_password:${config.sops.secrets."mysql/admin_password".path}"]; - RemainAfterExit = true; - # keep-sorted end - }; - }; - }; -} diff --git a/modules/services/network.nix b/modules/services/network.nix deleted file mode 100644 --- a/modules/services/network.nix +++ /dev/null @@ -1,85 +0,0 @@ -{ - flake.modules.nixos.network = { - # keep-sorted start - config, - lib, - vars, - # keep-sorted end - ... - }: let - inherit (lib) concatStringsSep; - inherit (vars) username; - in { - sops = let - hostname = config.networking.hostName; - in { - secrets = { - # keep-sorted start numeric=yes - "dns/${hostname}/dns_1" = {}; - "dns/${hostname}/dns_2" = {}; - "dns/${hostname}/dns_3" = {}; - "dns/${hostname}/dns_4" = {}; - # keep-sorted end - }; - - # Template for resolved.conf carrying dns servers from sops. - templates."resolved-dns.conf" = { - mode = "0440"; - group = "systemd-resolve"; - - content = let - secret = config.sops.placeholder; - in '' - [Resolve] - DNS=${concatStringsSep " " [ - secret."dns/${hostname}/dns_1" - secret."dns/${hostname}/dns_2" - secret."dns/${hostname}/dns_3" - secret."dns/${hostname}/dns_4" - ]} - ''; - }; - }; - - networking = { - useDHCP = false; - dhcpcd.enable = false; - - networkmanager = { - enable = true; - dns = "systemd-resolved"; - }; - }; - - users.users.${username}.extraGroups = ["networkmanager"]; - - services.resolved = { - enable = true; - settings.Resolve = { - DNSOverTLS = "opportunistic"; - - FallbackDNS = [ - # keep-sorted start - "1.0.0.1#cloudflare-dns.com" - "1.1.1.1#cloudflare-dns.com" - "2606:4700:4700::1001#cloudflare-dns.com" - "2606:4700:4700::1111#cloudflare-dns.com" - # keep-sorted end - ]; - }; - }; - - systemd = { - # Avoid blocking boot on network readiness. - network.wait-online.enable = false; - - services.systemd-resolved = { - wants = ["sops-install-secrets.service"]; - after = ["sops-install-secrets.service"]; - }; - }; - - # Install the resolved dns rendered from sops. - environment.etc."systemd/resolved.conf.d/00-dns.conf".source = config.sops.templates."resolved-dns.conf".path; - }; -} diff --git a/modules/services/pipewire.nix b/modules/services/pipewire.nix deleted file mode 100644 --- a/modules/services/pipewire.nix +++ /dev/null @@ -1,19 +0,0 @@ -{ - flake.modules.nixos.pipewire = { - security.rtkit.enable = true; - - services.pipewire = { - enable = true; - - # keep-sorted start - alsa.enable = true; - jack.enable = true; - pulse.enable = true; - wireplumber.enable = true; - # keep-sorted end - - # Keep 32-bit audio. - alsa.support32Bit = true; - }; - }; -} diff --git a/modules/services/podman.nix b/modules/services/podman.nix deleted file mode 100644 --- a/modules/services/podman.nix +++ /dev/null @@ -1,62 +0,0 @@ -{ - flake.modules.nixos.podman = { - # keep-sorted start - config, - pkgs, - vars, - # keep-sorted end - ... - }: let - inherit (vars) username; - hostname = config.networking.hostName; - secret = config.sops.placeholder; - in { - virtualisation = { - podman = { - enable = true; - # Expose docker-compatible socket for tooling that expects dockerd. - dockerSocket.enable = true; - }; - - # Disable the podman compose warning about external command execution. - containers.containersConf.settings.engine.compose_warning_logs = false; - }; - - sops.templates."podman-dns.conf" = { - mode = "0444"; - content = '' - [containers] - dns_servers = [ - "${secret."dns/${hostname}/dns_1"}", - "${secret."dns/${hostname}/dns_2"}", - "${secret."dns/${hostname}/dns_3"}", - "${secret."dns/${hostname}/dns_4"}", - ] - ''; - }; - - systemd.services.podman-dns-conf = { - after = ["sops-install-secrets.service"]; - wantedBy = ["multi-user.target"]; - - serviceConfig = { - ExecStart = pkgs.writeShellScript "podman-dns-conf" '' - set -eu - mkdir -p /etc/containers/containers.conf.d - ${pkgs.gnused}/bin/sed 's/#.*"/"/' ${config.sops.templates."podman-dns.conf".path} > /etc/containers/containers.conf.d/00-dns.conf - ''; - RemainAfterExit = true; - Type = "oneshot"; - }; - }; - - environment.systemPackages = [pkgs.podman-compose]; - - networking.firewall.extraInputRules = '' - iifname "podman*" udp dport 53 accept - iifname "podman*" tcp dport 53 accept - ''; - - users.users.${username}.extraGroups = ["podman"]; - }; -} diff --git a/modules/services/postgres.nix b/modules/services/postgres.nix deleted file mode 100644 --- a/modules/services/postgres.nix +++ /dev/null @@ -1,125 +0,0 @@ -{ - flake.modules.nixos.postgres = { - # keep-sorted start - config, - lib, - pkgs, - vars, - # keep-sorted end - ... - }: let - inherit (lib) mkForce; - - inherit (vars) username; - in { - sops.secrets."postgres/admin_password" = {}; - - services.postgresql = { - enable = true; - enableTCPIP = true; - - package = pkgs.postgresql_18; - - settings = { - # Listen only on local and WireGuard addresses. - listen_addresses = mkForce "127.0.0.1,::1,10.100.0.1"; - - shared_buffers = "256MB"; - maintenance_work_mem = "128MB"; - log_connections = true; - log_disconnections = true; - log_min_duration_statement = 1000; - }; - - ensureDatabases = [ - # keep-sorted start - "cloudbeaver" - "crowdsec" - "dockhand" - "gotify" - # keep-sorted end - ]; - - ensureUsers = [ - # keep-sorted start block=yes newline_separated=yes - { - ensureDBOwnership = true; - name = "cloudbeaver"; - } - - { - ensureDBOwnership = true; - name = "crowdsec"; - } - - { - ensureDBOwnership = true; - name = "dockhand"; - } - - { - ensureDBOwnership = true; - name = "gotify"; - } - - { - name = username; - } - # keep-sorted end - ]; - - authentication = '' - local all +container_login scram-sha-256 - local all all peer - host all all 127.0.0.1/32 scram-sha-256 - host all all ::1/128 scram-sha-256 - host all all 10.100.0.0/24 scram-sha-256 - ''; - }; - - services.postgresqlBackup = { - enable = true; - startAt = "*-*-* 02:00:00"; - - compression = "zstd"; - compressionLevel = 3; - }; - - networking.firewall.interfaces.wg0.allowedTCPPorts = [5432]; - - systemd.services.postgres-admin = { - # keep-sorted start block=yes newline_separated=yes - after = [ - "postgresql.service" - "sops-install-secrets.service" - ]; - - wantedBy = ["multi-user.target"]; - - wants = [ - "postgresql.service" - "sops-install-secrets.service" - ]; - # keep-sorted end - - script = '' - set -eu - password="$(${pkgs.coreutils}/bin/tr -d '\r\n' < "$CREDENTIALS_DIRECTORY/admin_password")" - ${config.services.postgresql.package}/bin/psql --dbname postgres --command "ALTER USER ${username} WITH SUPERUSER PASSWORD \''$${username}\''$''${password}\''$${username}\''$;" - ''; - - serviceConfig = { - # keep-sorted start - Group = "postgres"; - Type = "oneshot"; - User = "postgres"; - # keep-sorted end - - # keep-sorted start - LoadCredential = ["admin_password:${config.sops.secrets."postgres/admin_password".path}"]; - RemainAfterExit = true; - # keep-sorted end - }; - }; - }; -} diff --git a/modules/services/power-profiles-daemon.nix b/modules/services/power-profiles-daemon.nix deleted file mode 100644 --- a/modules/services/power-profiles-daemon.nix +++ /dev/null @@ -1,5 +0,0 @@ -{ - flake.modules.nixos.power-profiles-daemon = { - services.power-profiles-daemon.enable = true; - }; -} diff --git a/modules/services/printing.nix b/modules/services/printing.nix deleted file mode 100644 --- a/modules/services/printing.nix +++ /dev/null @@ -1,29 +0,0 @@ -{ - flake.modules.nixos.printing = {pkgs, ...}: { - services.printing = { - enable = true; - # Allow network clients to reach cups. - openFirewall = true; - - webInterface = false; - - drivers = with pkgs; [ - # keep-sorted start - foomatic-db-ppds - foomatic-db-ppds-withNonfreeDb - gutenprint - gutenprint-bin - splix - # keep-sorted end - ]; - - # Enable network printer discovery and share local queues by default. - browsing = true; - defaultShared = true; - }; - - fonts.enableGhostscriptFonts = true; - - programs.system-config-printer.enable = true; - }; -} diff --git a/modules/services/proton-wireguard.nix b/modules/services/proton-wireguard.nix deleted file mode 100644 --- a/modules/services/proton-wireguard.nix +++ /dev/null @@ -1,223 +0,0 @@ -{ - flake.modules.nixos.protonWireguard = { - # keep-sorted start - config, - lib, - pkgs, - # keep-sorted end - ... - }: let - inherit (lib) getExe getExe'; - - interface = "proton0"; - gateway = "10.2.0.1"; - routingTable = 51820; - - containerIPv4Subnet = "10.89.50.0/24"; - containerIPv4Gateway = "10.89.50.1"; - - ip = getExe' pkgs.iproute2 "ip"; - secret = config.sops.placeholder; - secretPrefix = "wireguard/${config.networking.hostName}/proton"; - - privateIPv4Subnets = [ - "10.0.0.0/8" - "172.16.0.0/12" - "192.168.0.0/16" - ]; - - routingTableString = toString routingTable; - - privateSubnetPostUpRules = builtins.concatStringsSep "\n" (map (subnet: '' - ${ip} -4 rule del from ${containerIPv4Subnet} to ${subnet} table main priority 900 2>/dev/null || true - ${ip} -4 rule add from ${containerIPv4Subnet} to ${subnet} table main priority 900 - '') - privateIPv4Subnets); - - privateSubnetPreDownRules = builtins.concatStringsSep "\n" (map (subnet: '' - ${ip} -4 rule del from ${containerIPv4Subnet} to ${subnet} table main priority 900 2>/dev/null || true - '') - privateIPv4Subnets); - - postUp = '' - ${ip} -4 route replace ${gateway} dev ${interface} - ${ip} -4 route replace default dev ${interface} table ${routingTableString} - ${privateSubnetPostUpRules} - ${ip} -4 rule del from ${containerIPv4Subnet} table ${routingTableString} priority 1000 2>/dev/null || true - ${ip} -4 rule add from ${containerIPv4Subnet} table ${routingTableString} priority 1000 - ''; - - preDown = '' - ${ip} -4 rule del from ${containerIPv4Subnet} table ${routingTableString} priority 1000 2>/dev/null || true - ${privateSubnetPreDownRules} - ${ip} -4 route del default dev ${interface} table ${routingTableString} 2>/dev/null || true - ${ip} -4 route del ${gateway} dev ${interface} 2>/dev/null || true - ''; - in { - sops = { - secrets = { - # keep-sorted start - "${secretPrefix}/address" = {}; - "${secretPrefix}/allowed_ips" = {}; - "${secretPrefix}/dns" = {}; - "${secretPrefix}/endpoint" = {}; - "${secretPrefix}/private_key" = {}; - "${secretPrefix}/proxy/password" = {}; - "${secretPrefix}/proxy/user" = {}; - "${secretPrefix}/public_key" = {}; - qbittorrent_proxy_path = {}; - # keep-sorted end - }; - - templates."${interface}.conf" = { - mode = "0400"; - content = '' - [Interface] - PrivateKey = ${secret."${secretPrefix}/private_key"} - Address = ${secret."${secretPrefix}/address"} - DNS = ${secret."${secretPrefix}/dns"} - MTU = 1420 - Table = ${routingTableString} - - [Peer] - PublicKey = ${secret."${secretPrefix}/public_key"} - AllowedIPs = ${secret."${secretPrefix}/allowed_ips"} - Endpoint = ${secret."${secretPrefix}/endpoint"} - PersistentKeepalive = 25 - ''; - }; - }; - - systemd.services."wg-quick-${interface}" = { - wants = ["sops-install-secrets.service"]; - after = ["sops-install-secrets.service"]; - }; - - systemd.services.proton-port-forward = { - description = "Maintain Proton VPN port forwarding"; - - after = [ - "nftables.service" - "sops-install-secrets.service" - "wg-quick-${interface}.service" - ]; - wantedBy = ["multi-user.target"]; - wants = [ - "sops-install-secrets.service" - "wg-quick-${interface}.service" - ]; - - environment = { - # keep-sorted start - CONTAINER_IPV4_SUBNET = containerIPv4Subnet; - PRIVATE_IPV4_SUBNETS = builtins.concatStringsSep " " privateIPv4Subnets; - PROTON_GATEWAY = gateway; - QBITTORRENT_CONTAINER = "qbittorrent"; - QBITTORRENT_NETWORK = "vpn"; - QUI_CONTAINER = "qui"; - QUI_NETWORK = "torrent"; - QUI_PORT = "7476"; - ROUTING_TABLE = routingTableString; - WIREGUARD_INTERFACE = interface; - # keep-sorted end - }; - - serviceConfig = { - ExecStart = getExe pkgs.proton-port-forward; - LoadCredential = [ - "qui_qbittorrent_proxy_path:${config.sops.secrets.qbittorrent_proxy_path.path}" - ]; - Restart = "always"; - RestartSec = "5s"; - }; - }; - - systemd.services.proton-indexer-proxy = { - description = "HTTP proxy for selected Prowlarr indexers over Proton VPN"; - - after = [ - "nftables.service" - "sops-install-secrets.service" - "wg-quick-${interface}.service" - ]; - wantedBy = ["multi-user.target"]; - wants = [ - "sops-install-secrets.service" - "wg-quick-${interface}.service" - ]; - - serviceConfig = { - DynamicUser = true; - ExecStart = pkgs.writeShellScript "proton-indexer-proxy" '' - set -eu - - config_file="$RUNTIME_DIRECTORY/tinyproxy.conf" - password_file="$CREDENTIALS_DIRECTORY/proxy_password" - user_file="$CREDENTIALS_DIRECTORY/proxy_user" - - cat > "$config_file" <> "$config_file" - fi - - exec ${getExe pkgs.tinyproxy} -d -c "$config_file" - ''; - LoadCredential = [ - "proxy_password:${config.sops.secrets."${secretPrefix}/proxy/password".path}" - "proxy_user:${config.sops.secrets."${secretPrefix}/proxy/user".path}" - ]; - Restart = "always"; - RestartSec = "5s"; - RuntimeDirectory = "proton-indexer-proxy"; - }; - }; - - networking = { - firewall.checkReversePath = "loose"; - - firewall.extraInputRules = '' - iifname "podman*" tcp dport 8888 accept - ''; - - nftables = { - enable = true; - ruleset = '' - table ip proton-wireguard-nat { - chain postrouting { - type nat hook postrouting priority srcnat; policy accept; - ip saddr ${containerIPv4Subnet} oifname "${interface}" masquerade - } - } - - table inet proton-wireguard-filter { - chain forward { - type filter hook forward priority -5; policy accept; - ct state established,related accept - iifname "podman*" oifname "podman*" ip saddr ${containerIPv4Subnet} accept - iifname "podman*" oifname "${interface}" ip saddr ${containerIPv4Subnet} accept - iifname "podman*" ip saddr ${containerIPv4Subnet} reject - } - } - ''; - }; - - wg-quick.interfaces.${interface} = { - autostart = true; - configFile = config.sops.templates."${interface}.conf".path; - inherit postUp preDown; - }; - }; - }; -} diff --git a/modules/services/scx-loader.nix b/modules/services/scx-loader.nix deleted file mode 100644 --- a/modules/services/scx-loader.nix +++ /dev/null @@ -1,15 +0,0 @@ -{ - flake.modules.nixos.scx-loader = {pkgs, ...}: { - services.scx-loader = { - enable = true; - schedsPackages = [pkgs.scx.rustscheds]; - - config = { - default_sched = "scx_lavd"; - default_mode = "Auto"; - - scheds.scx_lavd.auto_mode = ["--autopower"]; - }; - }; - }; -} diff --git a/modules/services/ssh-stunnel.nix b/modules/services/ssh-stunnel.nix deleted file mode 100644 --- a/modules/services/ssh-stunnel.nix +++ /dev/null @@ -1,16 +0,0 @@ -{ - flake.modules.nixos.ssh-stunnel = {vars, ...}: let - inherit (vars) groundDomain; - in { - services.stunnel = { - enable = true; - - clients.ssh-euclid = { - accept = "127.0.0.1:2201"; - checkHost = "euclid.${groundDomain}"; - connect = "euclid.${groundDomain}:22"; - sni = "euclid.${groundDomain}"; - }; - }; - }; -} diff --git a/modules/services/ssh.nix b/modules/services/ssh.nix deleted file mode 100644 --- a/modules/services/ssh.nix +++ /dev/null @@ -1,78 +0,0 @@ -{ - flake.modules.nixos.ssh = { - # keep-sorted start - config, - lib, - vars, - # keep-sorted end - ... - }: let - inherit (lib) mkForce; - - inherit (vars) username; - hostname = config.networking.hostName; - in { - sops.secrets."servers/${hostname}/public_ssh_key" = { - owner = username; - mode = "0400"; - }; - - services.openssh = { - enable = true; - - openFirewall = false; - ports = [2222]; - - listenAddresses = [ - {addr = "::1";} - ]; - - authorizedKeysFiles = mkForce [config.sops.secrets."servers/${hostname}/public_ssh_key".path]; - - settings = { - # keep-sorted start - AllowUsers = [username]; - PermitRootLogin = "no"; - # keep-sorted end - - # keep-sorted start - ChallengeResponseAuthentication = "no"; - KbdInteractiveAuthentication = false; - PasswordAuthentication = false; - PermitEmptyPasswords = "no"; - PubkeyAuthentication = "yes"; - # keep-sorted end - - # keep-sorted start - MaxAuthTries = 3; - MaxSessions = 4; - # keep-sorted end - - # keep-sorted start - AllowAgentForwarding = "no"; - AllowStreamLocalForwarding = "no"; - AllowTcpForwarding = "no"; - GatewayPorts = "no"; - PermitTunnel = "no"; - X11Forwarding = false; - # keep-sorted end - - # keep-sorted start - IgnoreRhosts = "yes"; - UseDns = false; - # keep-sorted end - - # keep-sorted start - ClientAliveCountMax = 0; - ClientAliveInterval = 300; - # keep-sorted end - - # keep-sorted start - Compression = "no"; - PrintMotd = false; - TCPKeepAlive = "no"; - # keep-sorted end - }; - }; - }; -} diff --git a/modules/services/timesyncd.nix b/modules/services/timesyncd.nix deleted file mode 100644 --- a/modules/services/timesyncd.nix +++ /dev/null @@ -1,12 +0,0 @@ -{ - flake.modules.nixos.timesyncd = { - services.timesyncd.servers = [ - # keep-sorted start numeric=yes - "server 0.pool.ntp.org" - "server 1.pool.ntp.org" - "server 2.pool.ntp.org" - "server 3.pool.ntp.org" - # keep-sorted end - ]; - }; -} diff --git a/modules/services/tlp.nix b/modules/services/tlp.nix deleted file mode 100644 --- a/modules/services/tlp.nix +++ /dev/null @@ -1,15 +0,0 @@ -{ - flake.modules.nixos.tlp = {lib, ...}: { - services = { - # Disable conflicting power management daemon. - power-profiles-daemon.enable = lib.mkForce false; - - tlp = { - enable = true; - - # Enable tlp power daemon for power-profiles-daemon compatibility. - pd.enable = true; - }; - }; - }; -} diff --git a/modules/services/tmp.nix b/modules/services/tmp.nix deleted file mode 100644 --- a/modules/services/tmp.nix +++ /dev/null @@ -1,5 +0,0 @@ -{ - flake.modules.nixos.tmp = { - boot.tmp.useTmpfs = true; - }; -} diff --git a/modules/services/traefik.nix b/modules/services/traefik.nix deleted file mode 100644 --- a/modules/services/traefik.nix +++ /dev/null @@ -1,284 +0,0 @@ -{ - flake.modules.nixos.traefik = { - # keep-sorted start - config, - vars, - # keep-sorted end - ... - }: let - secrets = config.sops.secrets; - templates = config.sops.templates; - - inherit (vars) groundDomain; - in { - sops = { - secrets = { - # keep-sorted start block=yes newline_separated=yes - "traefik/crowdsec_bouncer_key" = { - owner = "traefik"; - mode = "0400"; - }; - - "traefik/mail" = {}; - - "traefik/porkbun_api_key" = {}; - - "traefik/porkbun_secret_api_key" = {}; - # keep-sorted end - }; - - templates."traefik.env".content = '' - TRAEFIK_ACME_EMAIL=${config.sops.placeholder."traefik/mail"} - PORKBUN_API_KEY=${config.sops.placeholder."traefik/porkbun_api_key"} - PORKBUN_SECRET_API_KEY=${config.sops.placeholder."traefik/porkbun_secret_api_key"} - ''; - }; - - services = { - # keep-sorted start block=yes newline_separated=yes - traefik = { - enable = true; - group = "podman"; - environmentFiles = [templates."traefik.env".path]; - - staticConfigOptions = { - # keep-sorted start block=yes newline_separated=yes - accessLog.filePath = "/var/log/traefik/access.log"; - - api = { - dashboard = true; - insecure = false; - }; - - certificatesResolvers.myresolver.acme = { - dnsChallenge.provider = "porkbun"; - email = "\${TRAEFIK_ACME_EMAIL}"; - storage = "/var/lib/traefik/acme.json"; - }; - - entryPoints = { - # keep-sorted start block=yes newline_separated=yes - ssh.address = ":22"; - - web = { - address = ":80"; - - http = { - middlewares = ["crowdsec@file"]; - - redirections.entryPoint = { - to = "websecure"; - scheme = "https"; - }; - }; - }; - - websecure = { - address = ":443"; - - http = { - tls = { - certResolver = "myresolver"; - - domains = [ - { - main = "${groundDomain}"; - sans = ["*.${groundDomain}"]; - } - ]; - }; - - middlewares = ["crowdsec@file"]; - }; - - transport.respondingTimeouts = { - readTimeout = "600s"; - writeTimeout = "600s"; - idleTimeout = "600s"; - }; - }; - # keep-sorted end - }; - - experimental.plugins.bouncer = { - moduleName = "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"; - version = "v1.6.0"; - }; - - log = { - level = "INFO"; - filePath = "/var/log/traefik/traefik.log"; - }; - - providers.docker = { - endpoint = "unix:///run/podman/podman.sock"; - exposedByDefault = false; - network = "network"; - }; - # keep-sorted end - }; - - dynamicConfigOptions = { - http = { - middlewares = { - # keep-sorted start block=yes newline_separated=yes - authentik-proxy.forwardAuth = { - address = "http://[::1]:9005/outpost.goauthentik.io/auth/traefik"; - trustForwardHeader = true; - }; - - authentik.forwardAuth = { - address = "http://[::1]:9005/outpost.goauthentik.io/auth/traefik"; - trustForwardHeader = true; - authResponseHeaders = [ - # keep-sorted start - "X-authentik-email" - "X-authentik-entitlements" - "X-authentik-groups" - "X-authentik-jwt" - "X-authentik-meta-app" - "X-authentik-meta-jwks" - "X-authentik-meta-outpost" - "X-authentik-meta-provider" - "X-authentik-meta-version" - "X-authentik-name" - "X-authentik-uid" - "X-authentik-username" - # keep-sorted end - ]; - }; - - crowdsec.plugin.bouncer = { - enabled = true; - crowdsecMode = "appsec"; - crowdsecAppsecEnabled = true; - crowdsecAppsecHost = "127.0.0.1:7424"; - crowdsecAppsecKeyFile = secrets."traefik/crowdsec_bouncer_key".path; - crowdsecLapiUrl = "http://127.0.0.1:8080"; - crowdsecLapiKeyFile = secrets."traefik/crowdsec_bouncer_key".path; - }; - - redirect-to-https.redirectscheme = { - scheme = "https"; - permanent = true; - }; - # keep-sorted end - }; - - routers = { - # keep-sorted start block=yes newline_separated=yes - apprise = { - entryPoints = ["websecure"]; - middlewares = ["authentik@file"]; - rule = "Host(`apprise.${groundDomain}`)"; - service = "apprise"; - }; - - authentik = { - entryPoints = ["websecure"]; - rule = "Host(`authentik.${groundDomain}`)"; - service = "authentik"; - }; - - authentik-outpost = { - entryPoints = ["websecure"]; - priority = 15; - rule = "Host(`traefik.${groundDomain}`) && PathPrefix(`/outpost.goauthentik.io/`)"; - service = "authentik-outpost"; - }; - - cloudbeaver = { - entryPoints = ["websecure"]; - middlewares = ["authentik@file"]; - rule = "Host(`cloudbeaver.${groundDomain}`)"; - service = "cloudbeaver"; - }; - - dockhand = { - entryPoints = ["websecure"]; - rule = "Host(`dockhand.${groundDomain}`)"; - service = "dockhand"; - }; - - gotify = { - entryPoints = ["websecure"]; - rule = "Host(`gotify.${groundDomain}`)"; - service = "gotify"; - }; - - traefik-dashboard = { - entryPoints = ["websecure"]; - middlewares = ["authentik@file"]; - rule = "Host(`traefik.${groundDomain}`)"; - service = "api@internal"; - }; - # keep-sorted end - }; - - services = { - # keep-sorted start block=yes newline_separated=yes - apprise.loadBalancer.servers = [ - {url = "http://127.0.0.1:8000";} - ]; - - authentik-outpost.loadBalancer.servers = [ - {url = "http://[::1]:9005/outpost.goauthentik.io";} - ]; - - authentik.loadBalancer.servers = [ - {url = "http://[::1]:9000";} - ]; - - cloudbeaver.loadBalancer.servers = [ - {url = "http://127.0.0.1:8978";} - ]; - - dockhand.loadBalancer.servers = [ - {url = "http://127.0.0.1:3000";} - ]; - - gotify.loadBalancer.servers = [ - {url = "http://127.0.0.1:44407";} - ]; - # keep-sorted end - }; - }; - - tcp = { - routers.ssh-euclid = { - entryPoints = ["ssh"]; - rule = "HostSNI(`euclid.${groundDomain}`)"; - service = "ssh-euclid"; - tls.certResolver = "myresolver"; - }; - - services.ssh-euclid.loadBalancer.servers = [ - {address = "[::1]:2222";} - ]; - }; - }; - }; - # keep-sorted end - }; - - networking.firewall.allowedTCPPorts = [ - # keep-sorted start numeric=yes - 22 - 80 - 443 - # keep-sorted end - ]; - - systemd = { - tmpfiles.rules = ["d /var/log/traefik 0750 traefik traefik -"]; - - services.traefik = { - wants = ["podman.socket"]; - after = ["podman.socket"]; - - serviceConfig.TimeoutStopSec = "60s"; - stopIfChanged = false; - }; - }; - }; -} diff --git a/modules/services/udisks2.nix b/modules/services/udisks2.nix deleted file mode 100644 --- a/modules/services/udisks2.nix +++ /dev/null @@ -1,5 +0,0 @@ -{ - flake.modules.nixos.udisks2 = { - services.udisks2.enable = true; - }; -} diff --git a/modules/services/upower.nix b/modules/services/upower.nix deleted file mode 100644 --- a/modules/services/upower.nix +++ /dev/null @@ -1,5 +0,0 @@ -{ - flake.modules.nixos.upower = { - services.upower.enable = true; - }; -} diff --git a/modules/services/wifi.nix b/modules/services/wifi.nix deleted file mode 100644 --- a/modules/services/wifi.nix +++ /dev/null @@ -1,17 +0,0 @@ -{ - flake.modules.nixos.wifi = { - config = { - capabilities.wifi = true; - - networking = { - wireless.iwd.enable = true; - - networkmanager.wifi = { - backend = "iwd"; - powersave = false; - scanRandMacAddress = true; - }; - }; - }; - }; -} diff --git a/modules/services/wireguard.nix b/modules/services/wireguard.nix deleted file mode 100644 --- a/modules/services/wireguard.nix +++ /dev/null @@ -1,119 +0,0 @@ -{ - flake.modules.nixos.wireguard = { - # keep-sorted start - config, - lib, - # keep-sorted end - ... - }: let - inherit - (lib) - # keep-sorted start - mkIf - mkOption - types - # keep-sorted end - ; - - cfg = config.services.homelabWireguard; - in { - options.services.homelabWireguard = { - # keep-sorted start block=yes newline_separated=yes - address = mkOption { - description = "WireGuard interface address."; - - type = types.str; - example = "10.100.0.1/24"; - }; - - enable = mkOption { - description = "Enable the homelab WireGuard interface."; - - default = false; - type = types.bool; - }; - - interface = mkOption { - description = "WireGuard interface name."; - - default = "wg0"; - type = types.str; - }; - - listenPort = mkOption { - description = "WireGuard UDP listen port."; - - default = 51820; - type = types.port; - }; - - peers = mkOption { - default = []; - type = types.listOf (types.submodule { - options = { - publicKey = mkOption { - description = "Peer public key."; - - type = types.str; - }; - - allowedIPs = mkOption { - description = "Peer routes allowed through the tunnel."; - - type = types.listOf types.str; - example = ["10.100.0.2/32"]; - }; - - endpoint = mkOption { - description = "Optional peer endpoint."; - - default = null; - type = types.nullOr types.str; - }; - - persistentKeepalive = mkOption { - description = "Optional keepalive interval in seconds."; - - default = null; - type = types.nullOr types.ints.positive; - }; - }; - }); - description = "WireGuard peers."; - }; - - privateKeySecret = mkOption { - description = "Sops secret path containing the WireGuard private key."; - - type = types.str; - example = "wireguard/euclid/private_key"; - }; - # keep-sorted end - }; - - config = mkIf cfg.enable { - sops.secrets.${cfg.privateKeySecret} = {}; - - systemd.services."wireguard-${cfg.interface}" = { - wants = ["sops-install-secrets.service"]; - after = ["sops-install-secrets.service"]; - }; - - networking.wireguard.interfaces.${cfg.interface} = { - ips = [cfg.address]; - - inherit - (cfg) - # keep-sorted start - listenPort - peers - # keep-sorted end - ; - - privateKeyFile = config.sops.secrets.${cfg.privateKeySecret}.path; - }; - - networking.firewall.allowedUDPPorts = [cfg.listenPort]; - }; - }; -} diff --git a/modules/services/zram.nix b/modules/services/zram.nix deleted file mode 100644 --- a/modules/services/zram.nix +++ /dev/null @@ -1,8 +0,0 @@ -{ - flake.modules.nixos.zram = { - zramSwap = { - enable = true; - priority = 100; - }; - }; -} diff --git a/modules/desktop/hyprland/default.nix b/modules/desktop/hyprland/default.nix --- a/modules/desktop/hyprland/default.nix +++ b/modules/desktop/hyprland/default.nix @@ -3,7 +3,7 @@ hyprland.url = "github:hyprwm/Hyprland?ref=v0.55.4"; hylix = { - url = "github:adam01110/hylix"; + url = "git+https://tangled.org/adam0.dev/hylix"; inputs = { # keep-sorted start flake-parts.follows = "flake-parts"; diff --git a/modules/pkgs/preview/text.nix b/modules/pkgs/preview/text.nix --- a/modules/pkgs/preview/text.nix +++ b/modules/pkgs/preview/text.nix @@ -3,8 +3,10 @@ packages.text-preview = pkgs.writeShellApplication { name = "text-preview"; runtimeInputs = with pkgs; [ - file + # keep-sorted start bat + file + # keep-sorted end ]; text = '' path="''${1-}" diff --git a/modules/profiles/stylix/server.nix b/modules/profiles/stylix/server.nix --- a/modules/profiles/stylix/server.nix +++ b/modules/profiles/stylix/server.nix @@ -4,6 +4,15 @@ stylix = { fonts = { + # keep-sorted start block=yes + emoji = { + name = "Noto Color Emoji"; + package = pkgs.noto-fonts-color-emoji; + }; + monospace = { + name = "DejaVu Sans Mono"; + package = pkgs.dejavu_fonts; + }; sansSerif = { name = "DejaVu Sans"; package = pkgs.dejavu_fonts; @@ -12,14 +21,7 @@ name = "DejaVu Serif"; package = pkgs.dejavu_fonts; }; - monospace = { - name = "DejaVu Sans Mono"; - package = pkgs.dejavu_fonts; - }; - emoji = { - name = "Noto Color Emoji"; - package = pkgs.noto-fonts-color-emoji; - }; + # keep-sorted end }; targets.console.enable = true; diff --git a/modules/programs/cli/gen-license.nix b/modules/programs/cli/gen-license.nix new file mode 100644 --- /dev/null +++ b/modules/programs/cli/gen-license.nix @@ -0,0 +1,5 @@ +{ + flake.modules.homeManager.gen-license = {pkgs, ...}: { + home.packages = [pkgs.gen-license]; + }; +} diff --git a/modules/programs/cli/gh.nix b/modules/programs/cli/gh.nix --- a/modules/programs/cli/gh.nix +++ b/modules/programs/cli/gh.nix @@ -14,8 +14,17 @@ ghWrapper = writeShellApplication { name = "gh"; - runtimeInputs = [pkgs.coreutils]; + runtimeInputs = with pkgs; [ + # keep-sorted start + coreutils + jujutsu + # keep-sorted end + ]; text = '' + if [ -z "''${GIT_DIR:-}" ] && git_dir="$(jj git root 2>/dev/null)"; then + export GIT_DIR="$git_dir" + fi + GH_TOKEN="$(cat "${config.sops.secrets.github_token.path}")" export GH_TOKEN exec "${getExe pkgs.gh}" "$@" diff --git a/modules/programs/gui/tangled.nix b/modules/programs/gui/tangled.nix new file mode 100644 --- /dev/null +++ b/modules/programs/gui/tangled.nix @@ -0,0 +1,33 @@ +{ + flake.modules.homeManager.tangled = {pkgs, ...}: let + inherit + (pkgs) + # keep-sorted start + installShellFiles + runCommand + # keep-sorted end + ; + + tang = pkgs.nur.repos.adam0.tang; + tang-completions = runCommand "tang-completions" {nativeBuildInputs = [installShellFiles tang];} '' + installShellCompletion --cmd tang \ + --bash <(tang completion bash) \ + --fish <(tang completion fish) \ + --zsh <(tang completion zsh) + ''; + in { + home.packages = [tang]; + + programs.bash.initExtra = '' + source ${tang-completions}/share/bash-completion/completions/tang + ''; + + programs.fish.interactiveShellInit = '' + source ${tang-completions}/share/fish/vendor_completions.d/tang.fish + ''; + + programs.zsh.initContent = '' + source ${tang-completions}/share/zsh/site-functions/_tang + ''; + }; +} diff --git a/modules/services/data/btrfs-autoscrub.nix b/modules/services/data/btrfs-autoscrub.nix new file mode 100644 --- /dev/null +++ b/modules/services/data/btrfs-autoscrub.nix @@ -0,0 +1,7 @@ +{ + flake.modules.nixos.btrfs-autoscrub = { + services.btrfs.autoScrub = { + enable = true; + }; + }; +} diff --git a/modules/services/data/mysql.nix b/modules/services/data/mysql.nix new file mode 100644 --- /dev/null +++ b/modules/services/data/mysql.nix @@ -0,0 +1,90 @@ +{ + flake.modules.nixos.mysql = { + # keep-sorted start + config, + pkgs, + vars, + # keep-sorted end + ... + }: let + inherit (vars) username; + in { + sops.secrets."mysql/admin_password" = {}; + + services.mysql = { + enable = true; + package = pkgs.mariadb; + + settings = { + mysqld = { + # Firewall limits remote access to WireGuard. + bind-address = "0.0.0.0"; + innodb_buffer_pool_size = "512M"; + max_connections = 100; + slow_query_log = true; + long_query_time = 1; + }; + + mysqldump.max_allowed_packet = "64M"; + }; + }; + + services.mysqlBackup = { + enable = true; + calendar = "02:00:00"; + + compressionAlg = "zstd"; + compressionLevel = 3; + }; + + networking.firewall = { + interfaces.wg0.allowedTCPPorts = [3306]; + + extraInputRules = '' + # Allow containers to reach host MariaDB. + iifname "podman*" tcp dport 3306 accept + ''; + }; + + systemd.services.mysql-admin = { + # keep-sorted start block=yes newline_separated=yes + after = [ + "mysql.service" + "sops-install-secrets.service" + ]; + + wantedBy = ["multi-user.target"]; + + wants = [ + "mysql.service" + "sops-install-secrets.service" + ]; + # keep-sorted end + + script = '' + set -eu + password="$(${pkgs.coreutils}/bin/tr -d '\r\n' < "$CREDENTIALS_DIRECTORY/admin_password")" + escaped_password="''${password//\'/\'\'}" + ${config.services.mysql.package}/bin/mysql -e " + CREATE USER IF NOT EXISTS '${username}'@'127.0.0.1' IDENTIFIED BY '$escaped_password'; + ALTER USER '${username}'@'127.0.0.1' IDENTIFIED BY '$escaped_password'; + GRANT ALL PRIVILEGES ON *.* TO '${username}'@'127.0.0.1' WITH GRANT OPTION; + FLUSH PRIVILEGES; + " + ''; + + serviceConfig = { + # keep-sorted start + Group = "root"; + Type = "oneshot"; + User = "root"; + # keep-sorted end + + # keep-sorted start + LoadCredential = ["admin_password:${config.sops.secrets."mysql/admin_password".path}"]; + RemainAfterExit = true; + # keep-sorted end + }; + }; + }; +} diff --git a/modules/services/data/postgres.nix b/modules/services/data/postgres.nix new file mode 100644 --- /dev/null +++ b/modules/services/data/postgres.nix @@ -0,0 +1,125 @@ +{ + flake.modules.nixos.postgres = { + # keep-sorted start + config, + lib, + pkgs, + vars, + # keep-sorted end + ... + }: let + inherit (lib) mkForce; + + inherit (vars) username; + in { + sops.secrets."postgres/admin_password" = {}; + + services.postgresql = { + enable = true; + enableTCPIP = true; + + package = pkgs.postgresql_18; + + settings = { + # Listen only on local and WireGuard addresses. + listen_addresses = mkForce "127.0.0.1,::1,10.100.0.1"; + + shared_buffers = "256MB"; + maintenance_work_mem = "128MB"; + log_connections = true; + log_disconnections = true; + log_min_duration_statement = 1000; + }; + + ensureDatabases = [ + # keep-sorted start + "cloudbeaver" + "crowdsec" + "dockhand" + "gotify" + # keep-sorted end + ]; + + ensureUsers = [ + # keep-sorted start block=yes newline_separated=yes + { + ensureDBOwnership = true; + name = "cloudbeaver"; + } + + { + ensureDBOwnership = true; + name = "crowdsec"; + } + + { + ensureDBOwnership = true; + name = "dockhand"; + } + + { + ensureDBOwnership = true; + name = "gotify"; + } + + { + name = username; + } + # keep-sorted end + ]; + + authentication = '' + local all +container_login scram-sha-256 + local all all peer + host all all 127.0.0.1/32 scram-sha-256 + host all all ::1/128 scram-sha-256 + host all all 10.100.0.0/24 scram-sha-256 + ''; + }; + + services.postgresqlBackup = { + enable = true; + startAt = "*-*-* 02:00:00"; + + compression = "zstd"; + compressionLevel = 3; + }; + + networking.firewall.interfaces.wg0.allowedTCPPorts = [5432]; + + systemd.services.postgres-admin = { + # keep-sorted start block=yes newline_separated=yes + after = [ + "postgresql.service" + "sops-install-secrets.service" + ]; + + wantedBy = ["multi-user.target"]; + + wants = [ + "postgresql.service" + "sops-install-secrets.service" + ]; + # keep-sorted end + + script = '' + set -eu + password="$(${pkgs.coreutils}/bin/tr -d '\r\n' < "$CREDENTIALS_DIRECTORY/admin_password")" + ${config.services.postgresql.package}/bin/psql --dbname postgres --command "ALTER USER ${username} WITH SUPERUSER PASSWORD \''$${username}\''$''${password}\''$${username}\''$;" + ''; + + serviceConfig = { + # keep-sorted start + Group = "postgres"; + Type = "oneshot"; + User = "postgres"; + # keep-sorted end + + # keep-sorted start + LoadCredential = ["admin_password:${config.sops.secrets."postgres/admin_password".path}"]; + RemainAfterExit = true; + # keep-sorted end + }; + }; + }; +} diff --git a/modules/services/data/rclone.nix b/modules/services/data/rclone.nix new file mode 100644 --- /dev/null +++ b/modules/services/data/rclone.nix @@ -0,0 +1,130 @@ +{ + flake.modules.homeManager.rclone = { + # keep-sorted start + config, + lib, + pkgs, + vars, + # keep-sorted end + ... + }: let + inherit + (lib) + # keep-sorted start + getExe + mapAttrs' + nameValuePair + # keep-sorted end + ; + + inherit + (vars) + # keep-sorted start + groundDomain + username + # keep-sorted end + ; + + syncs = { + # keep-sorted start block=yes newline_separated=yes + screenshots = { + localPath = "${config.home.homeDirectory}/Pictures/screenshots"; + remotePath = "copyparty:/u/${username}/Pictures/screenshots"; + }; + + wallpapers = { + localPath = "${config.home.homeDirectory}/Pictures/wallpapers"; + remotePath = "copyparty:/u/${username}/Pictures/wallpapers"; + }; + # keep-sorted end + }; + + mkBisyncService = name: sync: { + Unit = { + After = ["rclone-config.service"]; + Description = "Bidirectional sync for copyparty ${name}"; + Requires = ["rclone-config.service"]; + }; + + Service = { + Environment = [ + "LOCAL_PATH=${sync.localPath}" + "REMOTE_PATH=${sync.remotePath}" + "WORK_DIR=${config.xdg.stateHome}/rclone/bisync/copyparty-${name}" + ]; + ExecStart = getExe pkgs.rclone-bisync-runner; + Type = "oneshot"; + }; + }; + + mkBisyncTimer = name: _: { + Unit.Description = "Bidirectional sync for copyparty ${name}"; + + Timer = { + OnBootSec = "5m"; + OnUnitActiveSec = "15m"; + Persistent = true; + }; + + Install.WantedBy = ["timers.target"]; + }; + in { + sops.secrets = { + # keep-sorted start + "rclone/copyparty/basic_auth_header" = {}; + "rclone/google/token" = {}; + # keep-sorted end + }; + + programs.rclone = { + enable = true; + + remotes = { + copyparty = { + config = { + type = "webdav"; + url = "https://copyparty.${groundDomain}"; + vendor = "other"; + }; + + mounts."/u/${username}" = { + enable = true; + mountPoint = "${config.home.homeDirectory}/Remote/copyparty"; + options.vfs-cache-mode = "writes"; + }; + + secrets.headers = config.sops.secrets."rclone/copyparty/basic_auth_header".path; + }; + + google = { + config = { + scope = "drive"; + type = "drive"; + }; + + mounts."" = { + enable = true; + mountPoint = "${config.home.homeDirectory}/Remote/google"; + options.vfs-cache-mode = "writes"; + }; + + secrets.token = config.sops.secrets."rclone/google/token".path; + }; + }; + }; + + systemd.user = { + services = + mapAttrs' ( + name: sync: nameValuePair "copyparty-${name}-bisync" (mkBisyncService name sync) + ) + syncs; + + timers = + mapAttrs' ( + name: sync: nameValuePair "copyparty-${name}-bisync" (mkBisyncTimer name sync) + ) + syncs; + }; + }; +} diff --git a/modules/services/desktop/envfs.nix b/modules/services/desktop/envfs.nix new file mode 100644 --- /dev/null +++ b/modules/services/desktop/envfs.nix @@ -0,0 +1,27 @@ +{self, ...}: { + flake.overlays.envfs = _final: prev: let + src = prev.fetchFromGitHub { + owner = "Mic92"; + repo = "envfs"; + rev = "1.2.0"; + hash = "sha256-hj/6zS9ebF0IDqgc1Dne59nWx80nk6jn2gj8BzQUFIQ="; + }; + in { + # Pin envfs until nixpkgs picks up the upstream mount helper fix. + envfs = prev.envfs.overrideAttrs (_oldAttrs: { + version = "1.2.0"; + inherit src; + cargoDeps = prev.rustPlatform.fetchCargoVendor { + inherit src; + hash = "sha256-dz3gpE464jnmSDsAsmJHcxUsEKeUURNoUjgGU2214Xg="; + }; + }); + }; + + flake.modules.nixos.envfs = { + nixpkgs.overlays = [self.overlays.envfs]; + + # Provide fhs-style paths for compatibility with legacy applications. + services.envfs.enable = true; + }; +} diff --git a/modules/services/desktop/evolution-data-server.nix b/modules/services/desktop/evolution-data-server.nix new file mode 100644 --- /dev/null +++ b/modules/services/desktop/evolution-data-server.nix @@ -0,0 +1,5 @@ +{ + flake.modules.nixos.evolution-data-server = { + services.gnome.evolution-data-server.enable = true; + }; +} diff --git a/modules/services/desktop/flatpak.nix b/modules/services/desktop/flatpak.nix new file mode 100644 --- /dev/null +++ b/modules/services/desktop/flatpak.nix @@ -0,0 +1,42 @@ +{inputs, ...}: { + flake-file.inputs.nix-flatpak.url = "github:gmodena/nix-flatpak"; + + flake.modules.nixos.flatpak = { + services.flatpak.enable = true; + }; + + flake.modules.homeManager.flatpak = {config, ...}: let + home = config.home.homeDirectory; + in { + imports = [inputs.nix-flatpak.homeManagerModules.nix-flatpak]; + + services.flatpak.overrides.global = { + # keep-sorted start block=yes newline_separated=yes + # Add icons and dconf to the extra files. + Context.filesystems = [ + # keep-sorted start + "${home}/.icons" + "xdg-config/dconf:ro" + # keep-sorted end + ]; + + # Export gtk, gdk, and qt variables. + Environment = { + # keep-sorted start + GDK_BACKEND = "wayland,x11,*"; + GDK_DEBUG = "portals"; + GDK_SCALE = "1"; + GSK_RENDERER = "vulkan"; + GTK_USE_PORTAL = "1"; + # keep-sorted end + + # keep-sorted start + QT_AUTO_SCREEN_SCALE_FACTOR = "1"; + QT_QPA_PLATFORM = "wayland;xcb"; + QT_WAYLAND_DISABLE_WINDOWDECORATION = "1"; + # keep-sorted end + }; + # keep-sorted end + }; + }; +} diff --git a/modules/services/desktop/geoclue.nix b/modules/services/desktop/geoclue.nix new file mode 100644 --- /dev/null +++ b/modules/services/desktop/geoclue.nix @@ -0,0 +1,20 @@ +{ + flake.modules.nixos.geoclue = {config, ...}: let + cfgWifi = config.capabilities.wifi; + in { + services.geoclue2 = { + enable = true; + + # Use wi-fi positioning only when wi-fi support is enabled for the host. + enableWifi = cfgWifi; + + # Disable radio/serial backends to avoid unnecessary hardware usage. + # keep-sorted start + enable3G = false; + enableCDMA = false; + enableModemGPS = false; + enableNmea = false; + # keep-sorted end + }; + }; +} diff --git a/modules/services/desktop/gnome-keyring.nix b/modules/services/desktop/gnome-keyring.nix new file mode 100644 --- /dev/null +++ b/modules/services/desktop/gnome-keyring.nix @@ -0,0 +1,20 @@ +{ + flake.modules.nixos.gnome-keyring = { + # keep-sorted start block=yes newline_separated=yes + security.pam.services = { + greetd.enableGnomeKeyring = true; + login.enableGnomeKeyring = true; + }; + + services.gnome = { + # keep-sorted start newline_separated=yes + # Disable the gcr SSH agent managed by GNOME. + gcr-ssh-agent.enable = false; + + # Start keyring services for secret storage. + gnome-keyring.enable = true; + # keep-sorted end + }; + # keep-sorted end + }; +} diff --git a/modules/services/desktop/gvfs.nix b/modules/services/desktop/gvfs.nix new file mode 100644 --- /dev/null +++ b/modules/services/desktop/gvfs.nix @@ -0,0 +1,11 @@ +{ + flake.modules.nixos.gvfs = {pkgs, ...}: { + # GIO/GVFS backends: trash, SMB/MTP/AFC, network mounts, and more. + services.gvfs = { + enable = true; + + # Use minimal gvfs without the full GNOME desktop. + package = pkgs.gvfs; + }; + }; +} diff --git a/modules/services/desktop/locate.nix b/modules/services/desktop/locate.nix new file mode 100644 --- /dev/null +++ b/modules/services/desktop/locate.nix @@ -0,0 +1,9 @@ +{ + flake.modules.nixos.locate = { + services.locate = { + enable = true; + + interval = "daily"; + }; + }; +} diff --git a/modules/services/desktop/tmp.nix b/modules/services/desktop/tmp.nix new file mode 100644 --- /dev/null +++ b/modules/services/desktop/tmp.nix @@ -0,0 +1,5 @@ +{ + flake.modules.nixos.tmp = { + boot.tmp.useTmpfs = true; + }; +} diff --git a/modules/services/desktop/udisks2.nix b/modules/services/desktop/udisks2.nix new file mode 100644 --- /dev/null +++ b/modules/services/desktop/udisks2.nix @@ -0,0 +1,5 @@ +{ + flake.modules.nixos.udisks2 = { + services.udisks2.enable = true; + }; +} diff --git a/modules/services/hardware/bluetooth.nix b/modules/services/hardware/bluetooth.nix new file mode 100644 --- /dev/null +++ b/modules/services/hardware/bluetooth.nix @@ -0,0 +1,17 @@ +{ + flake.modules.nixos.bluetooth = { + config = { + capabilities.bluetooth = true; + + hardware.bluetooth = { + enable = true; + + # Enable experimental features needed by some devices. + settings.General.Experimental = true; + + # Disable bluetooth power-on at boot to save battery. + powerOnBoot = false; + }; + }; + }; +} diff --git a/modules/services/hardware/libinput.nix b/modules/services/hardware/libinput.nix new file mode 100644 --- /dev/null +++ b/modules/services/hardware/libinput.nix @@ -0,0 +1,20 @@ +{ + flake.modules.nixos.libinput = { + services.libinput.enable = true; + }; + + flake.modules.nixos.roccat = { + environment.etc."libinput/local-overrides.quirks" = let + name = "ROCCAT ROCCAT Kain 100"; + in { + text = '' + [${name}] + MatchName=${name} + ModelBouncingKeys=1 + ''; + mode = "0644"; + user = "root"; + group = "root"; + }; + }; +} diff --git a/modules/services/hardware/pipewire.nix b/modules/services/hardware/pipewire.nix new file mode 100644 --- /dev/null +++ b/modules/services/hardware/pipewire.nix @@ -0,0 +1,19 @@ +{ + flake.modules.nixos.pipewire = { + security.rtkit.enable = true; + + services.pipewire = { + enable = true; + + # keep-sorted start + alsa.enable = true; + jack.enable = true; + pulse.enable = true; + wireplumber.enable = true; + # keep-sorted end + + # Keep 32-bit audio. + alsa.support32Bit = true; + }; + }; +} diff --git a/modules/services/hardware/power-profiles-daemon.nix b/modules/services/hardware/power-profiles-daemon.nix new file mode 100644 --- /dev/null +++ b/modules/services/hardware/power-profiles-daemon.nix @@ -0,0 +1,5 @@ +{ + flake.modules.nixos.power-profiles-daemon = { + services.power-profiles-daemon.enable = true; + }; +} diff --git a/modules/services/hardware/printing.nix b/modules/services/hardware/printing.nix new file mode 100644 --- /dev/null +++ b/modules/services/hardware/printing.nix @@ -0,0 +1,29 @@ +{ + flake.modules.nixos.printing = {pkgs, ...}: { + services.printing = { + enable = true; + # Allow network clients to reach cups. + openFirewall = true; + + webInterface = false; + + drivers = with pkgs; [ + # keep-sorted start + foomatic-db-ppds + foomatic-db-ppds-withNonfreeDb + gutenprint + gutenprint-bin + splix + # keep-sorted end + ]; + + # Enable network printer discovery and share local queues by default. + browsing = true; + defaultShared = true; + }; + + fonts.enableGhostscriptFonts = true; + + programs.system-config-printer.enable = true; + }; +} diff --git a/modules/services/hardware/tlp.nix b/modules/services/hardware/tlp.nix new file mode 100644 --- /dev/null +++ b/modules/services/hardware/tlp.nix @@ -0,0 +1,15 @@ +{ + flake.modules.nixos.tlp = {lib, ...}: { + services = { + # Disable conflicting power management daemon. + power-profiles-daemon.enable = lib.mkForce false; + + tlp = { + enable = true; + + # Enable tlp power daemon for power-profiles-daemon compatibility. + pd.enable = true; + }; + }; + }; +} diff --git a/modules/services/hardware/upower.nix b/modules/services/hardware/upower.nix new file mode 100644 --- /dev/null +++ b/modules/services/hardware/upower.nix @@ -0,0 +1,5 @@ +{ + flake.modules.nixos.upower = { + services.upower.enable = true; + }; +} diff --git a/modules/services/network/avahi.nix b/modules/services/network/avahi.nix new file mode 100644 --- /dev/null +++ b/modules/services/network/avahi.nix @@ -0,0 +1,13 @@ +{ + flake.modules.nixos.avahi = { + # mDNS/DNS-SD discovery for the local network. + services.avahi = { + enable = true; + + # keep-sorted start numeric=yes + nssmdns4 = true; + nssmdns6 = true; + # keep-sorted end + }; + }; +} diff --git a/modules/services/network/godns.nix b/modules/services/network/godns.nix new file mode 100644 --- /dev/null +++ b/modules/services/network/godns.nix @@ -0,0 +1,129 @@ +_: { + flake.overlays.godns = _final: prev: let + version = "3.4.1"; + + src = prev.fetchFromGitHub { + owner = "TimothyYe"; + repo = "godns"; + tag = "v${version}"; + hash = "sha256-LdMeb7pFYj+6HdUBgFkS756oox2HRgkwlHz65SgJoqY="; + }; + + packageLock = prev.fetchurl { + url = "https://raw.githubusercontent.com/tbutter/nixpkgs/a761abce85346f7de12fc7f2e792e6c7230f5217/pkgs/by-name/go/godns/package-lock.json"; + hash = "sha256-Lp3M2Ql4+Mr3qRdAqFAIE54BUwEIJWlsKiArZT41TXA="; + }; + in { + # Pin until NixOS/nixpkgs#518713 is merged. + godns = prev.godns.overrideAttrs (oldAttrs: { + inherit packageLock src version; + + ldflags = [ + "-s" + "-w" + "-X main.Version=${version}" + "-buildid=" + ]; + + npmDeps = prev.fetchNpmDeps { + src = prev.stdenv.mkDerivation { + name = "godns-web-src"; + inherit packageLock; + src = "${src}/web"; + + dontUnpack = true; + installPhase = '' + mkdir $out + cp -r $src/* $out + chmod +w $out + cp $packageLock $out/package-lock.json + ''; + }; + hash = "sha256-f8BU3HfQX9E+AFpXvNjRJNwT5nX1WwyinMRb7DP0FYU="; + }; + + postPatch = + (oldAttrs.postPatch or "") + + '' + cp ${packageLock} web/package-lock.json + substituteInPlace internal/provider/porkbun/porkbun_handler.go \ + --replace-fail 'ID string `json:"id,omitempty"`' 'ID interface{} `json:"id,omitempty"`' + ''; + + __darwinAllowLocalNetworking = true; + }); + }; + + flake.modules.nixos.godns = { + # keep-sorted start + config, + pkgs, + self, + vars, + # keep-sorted end + ... + }: let + inherit + (vars) + # keep-sorted start + groundDomain + orbitDomain + # keep-sorted end + ; + + secrets = config.sops.secrets; + in { + nixpkgs.overlays = [self.overlays.godns]; + + sops.secrets = { + # keep-sorted start + "godns/login_token" = {}; + "godns/password" = {}; + # keep-sorted end + }; + + services.godns = { + enable = true; + + settings = { + provider = "Porkbun"; + login_token_file = "$CREDENTIALS_DIRECTORY/login_token"; + password_file = "$CREDENTIALS_DIRECTORY/password"; + + domains = let + mkDomain = domain: { + domain_name = domain; + sub_domains = ["@" "*"]; + }; + in [ + # keep-sorted start + (mkDomain groundDomain) + (mkDomain orbitDomain) + # keep-sorted end + ]; + + resolver = "8.8.8.8"; + ip_type = "IPv4"; + interval = 300; + + ip_urls = [ + # keep-sorted start + "https://api-ipv4.ip.sb/ip" + "https://api.ip.sb/ip" + "https://api.ipify.org" + "https://myip.biturl.top" + # keep-sorted end + ]; + }; + + loadCredential = [ + # keep-sorted start + "login_token:${secrets."godns/login_token".path}" + "password:${secrets."godns/password".path}" + # keep-sorted end + ]; + }; + + systemd.services.godns.environment.SSL_CERT_FILE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"; + }; +} diff --git a/modules/services/network/network.nix b/modules/services/network/network.nix new file mode 100644 --- /dev/null +++ b/modules/services/network/network.nix @@ -0,0 +1,85 @@ +{ + flake.modules.nixos.network = { + # keep-sorted start + config, + lib, + vars, + # keep-sorted end + ... + }: let + inherit (lib) concatStringsSep; + inherit (vars) username; + in { + sops = let + hostname = config.networking.hostName; + in { + secrets = { + # keep-sorted start numeric=yes + "dns/${hostname}/dns_1" = {}; + "dns/${hostname}/dns_2" = {}; + "dns/${hostname}/dns_3" = {}; + "dns/${hostname}/dns_4" = {}; + # keep-sorted end + }; + + # Template for resolved.conf carrying dns servers from sops. + templates."resolved-dns.conf" = { + mode = "0440"; + group = "systemd-resolve"; + + content = let + secret = config.sops.placeholder; + in '' + [Resolve] + DNS=${concatStringsSep " " [ + secret."dns/${hostname}/dns_1" + secret."dns/${hostname}/dns_2" + secret."dns/${hostname}/dns_3" + secret."dns/${hostname}/dns_4" + ]} + ''; + }; + }; + + networking = { + useDHCP = false; + dhcpcd.enable = false; + + networkmanager = { + enable = true; + dns = "systemd-resolved"; + }; + }; + + users.users.${username}.extraGroups = ["networkmanager"]; + + services.resolved = { + enable = true; + settings.Resolve = { + DNSOverTLS = "opportunistic"; + + FallbackDNS = [ + # keep-sorted start + "1.0.0.1#cloudflare-dns.com" + "1.1.1.1#cloudflare-dns.com" + "2606:4700:4700::1001#cloudflare-dns.com" + "2606:4700:4700::1111#cloudflare-dns.com" + # keep-sorted end + ]; + }; + }; + + systemd = { + # Avoid blocking boot on network readiness. + network.wait-online.enable = false; + + services.systemd-resolved = { + wants = ["sops-install-secrets.service"]; + after = ["sops-install-secrets.service"]; + }; + }; + + # Install the resolved dns rendered from sops. + environment.etc."systemd/resolved.conf.d/00-dns.conf".source = config.sops.templates."resolved-dns.conf".path; + }; +} diff --git a/modules/services/network/proton-wireguard.nix b/modules/services/network/proton-wireguard.nix new file mode 100644 --- /dev/null +++ b/modules/services/network/proton-wireguard.nix @@ -0,0 +1,223 @@ +{ + flake.modules.nixos.protonWireguard = { + # keep-sorted start + config, + lib, + pkgs, + # keep-sorted end + ... + }: let + inherit (lib) getExe getExe'; + + interface = "proton0"; + gateway = "10.2.0.1"; + routingTable = 51820; + + containerIPv4Subnet = "10.89.50.0/24"; + containerIPv4Gateway = "10.89.50.1"; + + ip = getExe' pkgs.iproute2 "ip"; + secret = config.sops.placeholder; + secretPrefix = "wireguard/${config.networking.hostName}/proton"; + + privateIPv4Subnets = [ + "10.0.0.0/8" + "172.16.0.0/12" + "192.168.0.0/16" + ]; + + routingTableString = toString routingTable; + + privateSubnetPostUpRules = builtins.concatStringsSep "\n" (map (subnet: '' + ${ip} -4 rule del from ${containerIPv4Subnet} to ${subnet} table main priority 900 2>/dev/null || true + ${ip} -4 rule add from ${containerIPv4Subnet} to ${subnet} table main priority 900 + '') + privateIPv4Subnets); + + privateSubnetPreDownRules = builtins.concatStringsSep "\n" (map (subnet: '' + ${ip} -4 rule del from ${containerIPv4Subnet} to ${subnet} table main priority 900 2>/dev/null || true + '') + privateIPv4Subnets); + + postUp = '' + ${ip} -4 route replace ${gateway} dev ${interface} + ${ip} -4 route replace default dev ${interface} table ${routingTableString} + ${privateSubnetPostUpRules} + ${ip} -4 rule del from ${containerIPv4Subnet} table ${routingTableString} priority 1000 2>/dev/null || true + ${ip} -4 rule add from ${containerIPv4Subnet} table ${routingTableString} priority 1000 + ''; + + preDown = '' + ${ip} -4 rule del from ${containerIPv4Subnet} table ${routingTableString} priority 1000 2>/dev/null || true + ${privateSubnetPreDownRules} + ${ip} -4 route del default dev ${interface} table ${routingTableString} 2>/dev/null || true + ${ip} -4 route del ${gateway} dev ${interface} 2>/dev/null || true + ''; + in { + sops = { + secrets = { + # keep-sorted start + "${secretPrefix}/address" = {}; + "${secretPrefix}/allowed_ips" = {}; + "${secretPrefix}/dns" = {}; + "${secretPrefix}/endpoint" = {}; + "${secretPrefix}/private_key" = {}; + "${secretPrefix}/proxy/password" = {}; + "${secretPrefix}/proxy/user" = {}; + "${secretPrefix}/public_key" = {}; + qbittorrent_proxy_path = {}; + # keep-sorted end + }; + + templates."${interface}.conf" = { + mode = "0400"; + content = '' + [Interface] + PrivateKey = ${secret."${secretPrefix}/private_key"} + Address = ${secret."${secretPrefix}/address"} + DNS = ${secret."${secretPrefix}/dns"} + MTU = 1420 + Table = ${routingTableString} + + [Peer] + PublicKey = ${secret."${secretPrefix}/public_key"} + AllowedIPs = ${secret."${secretPrefix}/allowed_ips"} + Endpoint = ${secret."${secretPrefix}/endpoint"} + PersistentKeepalive = 25 + ''; + }; + }; + + systemd.services."wg-quick-${interface}" = { + wants = ["sops-install-secrets.service"]; + after = ["sops-install-secrets.service"]; + }; + + systemd.services.proton-port-forward = { + description = "Maintain Proton VPN port forwarding"; + + after = [ + "nftables.service" + "sops-install-secrets.service" + "wg-quick-${interface}.service" + ]; + wantedBy = ["multi-user.target"]; + wants = [ + "sops-install-secrets.service" + "wg-quick-${interface}.service" + ]; + + environment = { + # keep-sorted start + CONTAINER_IPV4_SUBNET = containerIPv4Subnet; + PRIVATE_IPV4_SUBNETS = builtins.concatStringsSep " " privateIPv4Subnets; + PROTON_GATEWAY = gateway; + QBITTORRENT_CONTAINER = "qbittorrent"; + QBITTORRENT_NETWORK = "vpn"; + QUI_CONTAINER = "qui"; + QUI_NETWORK = "torrent"; + QUI_PORT = "7476"; + ROUTING_TABLE = routingTableString; + WIREGUARD_INTERFACE = interface; + # keep-sorted end + }; + + serviceConfig = { + ExecStart = getExe pkgs.proton-port-forward; + LoadCredential = [ + "qui_qbittorrent_proxy_path:${config.sops.secrets.qbittorrent_proxy_path.path}" + ]; + Restart = "always"; + RestartSec = "5s"; + }; + }; + + systemd.services.proton-indexer-proxy = { + description = "HTTP proxy for selected Prowlarr indexers over Proton VPN"; + + after = [ + "nftables.service" + "sops-install-secrets.service" + "wg-quick-${interface}.service" + ]; + wantedBy = ["multi-user.target"]; + wants = [ + "sops-install-secrets.service" + "wg-quick-${interface}.service" + ]; + + serviceConfig = { + DynamicUser = true; + ExecStart = pkgs.writeShellScript "proton-indexer-proxy" '' + set -eu + + config_file="$RUNTIME_DIRECTORY/tinyproxy.conf" + password_file="$CREDENTIALS_DIRECTORY/proxy_password" + user_file="$CREDENTIALS_DIRECTORY/proxy_user" + + cat > "$config_file" <> "$config_file" + fi + + exec ${getExe pkgs.tinyproxy} -d -c "$config_file" + ''; + LoadCredential = [ + "proxy_password:${config.sops.secrets."${secretPrefix}/proxy/password".path}" + "proxy_user:${config.sops.secrets."${secretPrefix}/proxy/user".path}" + ]; + Restart = "always"; + RestartSec = "5s"; + RuntimeDirectory = "proton-indexer-proxy"; + }; + }; + + networking = { + firewall.checkReversePath = "loose"; + + firewall.extraInputRules = '' + iifname "podman*" tcp dport 8888 accept + ''; + + nftables = { + enable = true; + ruleset = '' + table ip proton-wireguard-nat { + chain postrouting { + type nat hook postrouting priority srcnat; policy accept; + ip saddr ${containerIPv4Subnet} oifname "${interface}" masquerade + } + } + + table inet proton-wireguard-filter { + chain forward { + type filter hook forward priority -5; policy accept; + ct state established,related accept + iifname "podman*" oifname "podman*" ip saddr ${containerIPv4Subnet} accept + iifname "podman*" oifname "${interface}" ip saddr ${containerIPv4Subnet} accept + iifname "podman*" ip saddr ${containerIPv4Subnet} reject + } + } + ''; + }; + + wg-quick.interfaces.${interface} = { + autostart = true; + configFile = config.sops.templates."${interface}.conf".path; + inherit postUp preDown; + }; + }; + }; +} diff --git a/modules/services/network/ssh-stunnel.nix b/modules/services/network/ssh-stunnel.nix new file mode 100644 --- /dev/null +++ b/modules/services/network/ssh-stunnel.nix @@ -0,0 +1,16 @@ +{ + flake.modules.nixos.ssh-stunnel = {vars, ...}: let + inherit (vars) groundDomain; + in { + services.stunnel = { + enable = true; + + clients.ssh-euclid = { + accept = "127.0.0.1:2201"; + checkHost = "euclid.${groundDomain}"; + connect = "euclid.${groundDomain}:22"; + sni = "euclid.${groundDomain}"; + }; + }; + }; +} diff --git a/modules/services/network/ssh.nix b/modules/services/network/ssh.nix new file mode 100644 --- /dev/null +++ b/modules/services/network/ssh.nix @@ -0,0 +1,78 @@ +{ + flake.modules.nixos.ssh = { + # keep-sorted start + config, + lib, + vars, + # keep-sorted end + ... + }: let + inherit (lib) mkForce; + + inherit (vars) username; + hostname = config.networking.hostName; + in { + sops.secrets."servers/${hostname}/public_ssh_key" = { + owner = username; + mode = "0400"; + }; + + services.openssh = { + enable = true; + + openFirewall = false; + ports = [2222]; + + listenAddresses = [ + {addr = "::1";} + ]; + + authorizedKeysFiles = mkForce [config.sops.secrets."servers/${hostname}/public_ssh_key".path]; + + settings = { + # keep-sorted start + AllowUsers = [username]; + PermitRootLogin = "no"; + # keep-sorted end + + # keep-sorted start + ChallengeResponseAuthentication = "no"; + KbdInteractiveAuthentication = false; + PasswordAuthentication = false; + PermitEmptyPasswords = "no"; + PubkeyAuthentication = "yes"; + # keep-sorted end + + # keep-sorted start + MaxAuthTries = 3; + MaxSessions = 4; + # keep-sorted end + + # keep-sorted start + AllowAgentForwarding = "no"; + AllowStreamLocalForwarding = "no"; + AllowTcpForwarding = "no"; + GatewayPorts = "no"; + PermitTunnel = "no"; + X11Forwarding = false; + # keep-sorted end + + # keep-sorted start + IgnoreRhosts = "yes"; + UseDns = false; + # keep-sorted end + + # keep-sorted start + ClientAliveCountMax = 0; + ClientAliveInterval = 300; + # keep-sorted end + + # keep-sorted start + Compression = "no"; + PrintMotd = false; + TCPKeepAlive = "no"; + # keep-sorted end + }; + }; + }; +} diff --git a/modules/services/network/timesyncd.nix b/modules/services/network/timesyncd.nix new file mode 100644 --- /dev/null +++ b/modules/services/network/timesyncd.nix @@ -0,0 +1,12 @@ +{ + flake.modules.nixos.timesyncd = { + services.timesyncd.servers = [ + # keep-sorted start numeric=yes + "server 0.pool.ntp.org" + "server 1.pool.ntp.org" + "server 2.pool.ntp.org" + "server 3.pool.ntp.org" + # keep-sorted end + ]; + }; +} diff --git a/modules/services/network/wifi.nix b/modules/services/network/wifi.nix new file mode 100644 --- /dev/null +++ b/modules/services/network/wifi.nix @@ -0,0 +1,17 @@ +{ + flake.modules.nixos.wifi = { + config = { + capabilities.wifi = true; + + networking = { + wireless.iwd.enable = true; + + networkmanager.wifi = { + backend = "iwd"; + powersave = false; + scanRandMacAddress = true; + }; + }; + }; + }; +} diff --git a/modules/services/network/wireguard.nix b/modules/services/network/wireguard.nix new file mode 100644 --- /dev/null +++ b/modules/services/network/wireguard.nix @@ -0,0 +1,119 @@ +{ + flake.modules.nixos.wireguard = { + # keep-sorted start + config, + lib, + # keep-sorted end + ... + }: let + inherit + (lib) + # keep-sorted start + mkIf + mkOption + types + # keep-sorted end + ; + + cfg = config.services.homelabWireguard; + in { + options.services.homelabWireguard = { + # keep-sorted start block=yes newline_separated=yes + address = mkOption { + description = "WireGuard interface address."; + + type = types.str; + example = "10.100.0.1/24"; + }; + + enable = mkOption { + description = "Enable the homelab WireGuard interface."; + + default = false; + type = types.bool; + }; + + interface = mkOption { + description = "WireGuard interface name."; + + default = "wg0"; + type = types.str; + }; + + listenPort = mkOption { + description = "WireGuard UDP listen port."; + + default = 51820; + type = types.port; + }; + + peers = mkOption { + default = []; + type = types.listOf (types.submodule { + options = { + publicKey = mkOption { + description = "Peer public key."; + + type = types.str; + }; + + allowedIPs = mkOption { + description = "Peer routes allowed through the tunnel."; + + type = types.listOf types.str; + example = ["10.100.0.2/32"]; + }; + + endpoint = mkOption { + description = "Optional peer endpoint."; + + default = null; + type = types.nullOr types.str; + }; + + persistentKeepalive = mkOption { + description = "Optional keepalive interval in seconds."; + + default = null; + type = types.nullOr types.ints.positive; + }; + }; + }); + description = "WireGuard peers."; + }; + + privateKeySecret = mkOption { + description = "Sops secret path containing the WireGuard private key."; + + type = types.str; + example = "wireguard/euclid/private_key"; + }; + # keep-sorted end + }; + + config = mkIf cfg.enable { + sops.secrets.${cfg.privateKeySecret} = {}; + + systemd.services."wireguard-${cfg.interface}" = { + wants = ["sops-install-secrets.service"]; + after = ["sops-install-secrets.service"]; + }; + + networking.wireguard.interfaces.${cfg.interface} = { + ips = [cfg.address]; + + inherit + (cfg) + # keep-sorted start + listenPort + peers + # keep-sorted end + ; + + privateKeyFile = config.sops.secrets.${cfg.privateKeySecret}.path; + }; + + networking.firewall.allowedUDPPorts = [cfg.listenPort]; + }; + }; +} diff --git a/modules/services/performance/ananicy.nix b/modules/services/performance/ananicy.nix new file mode 100644 --- /dev/null +++ b/modules/services/performance/ananicy.nix @@ -0,0 +1,10 @@ +{ + flake.modules.nixos.ananicy = {pkgs, ...}: { + services.ananicy = { + enable = true; + package = pkgs.ananicy-cpp; + + rulesProvider = pkgs.ananicy-rules-cachyos; + }; + }; +} diff --git a/modules/services/performance/bpftune.nix b/modules/services/performance/bpftune.nix new file mode 100644 --- /dev/null +++ b/modules/services/performance/bpftune.nix @@ -0,0 +1,5 @@ +{ + flake.modules.nixos.bpftune = { + services.bpftune.enable = true; + }; +} diff --git a/modules/services/performance/scx-loader.nix b/modules/services/performance/scx-loader.nix new file mode 100644 --- /dev/null +++ b/modules/services/performance/scx-loader.nix @@ -0,0 +1,15 @@ +{ + flake.modules.nixos.scx-loader = {pkgs, ...}: { + services.scx-loader = { + enable = true; + schedsPackages = [pkgs.scx.rustscheds]; + + config = { + default_sched = "scx_lavd"; + default_mode = "Auto"; + + scheds.scx_lavd.auto_mode = ["--autopower"]; + }; + }; + }; +} diff --git a/modules/services/performance/zram.nix b/modules/services/performance/zram.nix new file mode 100644 --- /dev/null +++ b/modules/services/performance/zram.nix @@ -0,0 +1,8 @@ +{ + flake.modules.nixos.zram = { + zramSwap = { + enable = true; + priority = 100; + }; + }; +} diff --git a/modules/services/security/authentik.nix b/modules/services/security/authentik.nix new file mode 100644 --- /dev/null +++ b/modules/services/security/authentik.nix @@ -0,0 +1,77 @@ +{inputs, ...}: { + flake-file.inputs.authentik-nix = { + url = "github:nix-community/authentik-nix"; + inputs.flake-parts.follows = "flake-parts"; + }; + + flake.modules.nixos.authentik = { + # keep-sorted start + config, + vars, + # keep-sorted end + ... + }: let + inherit (vars) groundDomain; + in { + imports = [inputs.authentik-nix.nixosModules.default]; + + sops = { + secrets = { + # keep-sorted start + "authentik/proxy_token" = {}; + "authentik/secret_key" = {}; + # keep-sorted end + + # keep-sorted start + "authentik/email/from" = {}; + "authentik/email/host" = {}; + "authentik/email/password" = {}; + "authentik/email/username" = {}; + # keep-sorted end + }; + + templates = { + "authentik.env".content = '' + AUTHENTIK_SECRET_KEY=${config.sops.placeholder."authentik/secret_key"} + AUTHENTIK_EMAIL__HOST=${config.sops.placeholder."authentik/email/host"} + AUTHENTIK_EMAIL__USERNAME=${config.sops.placeholder."authentik/email/username"} + AUTHENTIK_EMAIL__PASSWORD=${config.sops.placeholder."authentik/email/password"} + AUTHENTIK_EMAIL__FROM=${config.sops.placeholder."authentik/email/from"} + ''; + + "authentik-proxy.env".content = '' + AUTHENTIK_HOST=https://authentik.${groundDomain} + AUTHENTIK_TOKEN=${config.sops.placeholder."authentik/proxy_token"} + ''; + }; + }; + + services = { + authentik = { + enable = true; + environmentFile = config.sops.templates."authentik.env".path; + + settings = { + avatars = "gravatar"; + + # Disable unrequired features. + disable_startup_analytics = true; + disable_update_check = true; + error_reporting.enabled = false; + + email = { + port = 465; + use_ssl = true; + }; + }; + }; + + authentik-proxy = { + enable = true; + environmentFile = config.sops.templates."authentik-proxy.env".path; + }; + }; + + systemd.services.authentik-worker.serviceConfig.TimeoutStopSec = "60s"; + }; +} diff --git a/modules/services/security/crowdsec.nix b/modules/services/security/crowdsec.nix new file mode 100644 --- /dev/null +++ b/modules/services/security/crowdsec.nix @@ -0,0 +1,386 @@ +{inputs, ...}: { + flake-file = { + inputs.nixpkgs-crowdsec = { + url = "github:TornaxO7/nixpkgs/crowdsec"; + }; + + inputs.nixpkgs-crowdsec-blocklist-import = { + url = "github:gaelj/nixpkgs/init-crowdsec-blocklist-import"; + }; + }; + + flake.overlays.crowdsec = final: _prev: let + inherit (final.stdenv.hostPlatform) system; + crowdsecPkgs = inputs.nixpkgs-crowdsec.legacyPackages.${system}; + in { + inherit + (crowdsecPkgs) + # keep-sorted start + crowdsec + crowdsec-firewall-bouncer + # keep-sorted end + ; + }; + + flake.modules.nixos.crowdsec-base = { + # keep-sorted start + config, + self, + # keep-sorted end + ... + }: { + nixpkgs.overlays = [self.overlays.crowdsec]; + + disabledModules = [ + # keep-sorted start + "services/security/crowdsec-firewall-bouncer.nix" + "services/security/crowdsec.nix" + # keep-sorted end + ]; + + imports = [ + # keep-sorted start + "${inputs.nixpkgs-crowdsec-blocklist-import}/nixos/modules/services/security/crowdsec-blocklist-import.nix" + "${inputs.nixpkgs-crowdsec}/nixos/modules/services/security/crowdsec-firewall-bouncer.nix" + "${inputs.nixpkgs-crowdsec}/nixos/modules/services/security/crowdsec.nix" + # keep-sorted end + ]; + + services.crowdsec = { + enable = true; + autoUpdateService = true; + openFirewall = false; + + hub = { + collections = [ + # keep-sorted start + "LePresidente/jellyfin" + "LePresidente/jellyseerr" + "baudneo/gotify" + "crowdsecurity/appsec-generic-rules" + "crowdsecurity/appsec-virtual-patching" + "crowdsecurity/http-cve" + "crowdsecurity/linux" + "crowdsecurity/sshd" + "crowdsecurity/traefik" + "crowdsecurity/whitelist-good-actors" + "firix/authentik" + # keep-sorted end + ]; + }; + + settings = { + acquisitions = [ + { + journalctl_filter = ["_SYSTEMD_UNIT=sshd.service"]; + labels.type = "syslog"; + source = "journalctl"; + } + ]; + }; + }; + + users = { + groups.${config.services.crowdsec.group} = {}; + + users.${config.services.crowdsec.user} = { + group = config.services.crowdsec.group; + isSystemUser = true; + }; + }; + }; + + flake.modules.nixos.crowdsec-agent = {self, ...}: { + imports = [self.modules.nixos.crowdsec-base]; + + services.crowdsec.settings.config.api.server.enable = false; + }; + + flake.modules.nixos.crowdsec-server = { + # keep-sorted start + config, + lib, + pkgs, + self, + # keep-sorted end + ... + }: let + inherit + (lib) + # keep-sorted start + getExe + mkForce + # keep-sorted end + ; + + secrets = config.sops.secrets; + templates = config.sops.templates; + + dataDir = "/var/lib/crowdsec/data"; + gotifyUrl = "http://127.0.0.1:44407/message"; + + proxyPort = "12346"; + + setupDeps = [ + "postgresql.service" + "sops-install-secrets.service" + ]; + + setupUnit = { + after = setupDeps; + wants = setupDeps; + }; + in { + imports = [self.modules.nixos.crowdsec-base]; + + sops = { + secrets = { + # keep-sorted start + "crowdsec/console_enroll_key" = {}; + "crowdsec/gotify_api_key" = {}; + "traefik/crowdsec_bouncer_key" = {}; + # keep-sorted end + }; + + templates = { + "crowdsec-blocklist-import-env" = { + mode = "0640"; + owner = config.services.crowdsec.user; + group = config.services.crowdsec.group; + path = "/etc/crowdsec/blocklist-import.env"; + content = '' + WEBHOOK_TYPE: "generic" + WEBHOOK_URL=http://127.0.0.1:${proxyPort} + ''; + }; + + "crowdsec-gotify-notification" = { + mode = "0640"; + owner = config.services.crowdsec.user; + group = config.services.crowdsec.group; + path = "/etc/crowdsec/notifications/gotify-alerts.yaml"; + content = '' + type: http + name: gotify + log_level: info + group_threshold: 1 + url: ${gotifyUrl} + method: POST + headers: + X-Gotify-Key: ${config.sops.placeholder."crowdsec/gotify_api_key"} + Content-Type: application/json + format: | + {{ range . -}} + {{ $alert := . -}} + {{ $scenario := $alert.GetScenario -}} + {{ $source := $alert.GetValue -}} + { + "extras": { + "client::display": { + "contentType": "text/markdown" + } + }, + "priority": 3, + "title": "CrowdSec Alert", + "message": {{ printf "**Scenario:** `%s`\n\n**IP:** `%s`\n\n**Machine:** `%s`" $scenario $source $alert.MachineID | toJson }} + } + {{ end -}} + ''; + }; + }; + }; + + services = { + crowdsec.settings = { + config = { + api.server.online_client.credentials_path = "${dataDir}/online_api_credentials.yaml"; + + db_config = { + db_name = "crowdsec"; + db_path = "/run/postgresql"; + type = "pgx"; + user = "crowdsec"; + }; + }; + + console.enrollKeyFile = secrets."crowdsec/console_enroll_key".path; + + profiles = [ + { + filters = [''Alert.GetScenario() != "" && !(Alert.GetScenario() contains "external/blocklist")'']; + name = "all_scenario_notifications"; + notifications = ["gotify"]; + on_success = "continue"; + } + + { + decisions = [ + { + duration = "4h"; + type = "ban"; + } + ]; + + filters = [''Alert.Remediation == true && Alert.GetScope() == "Ip"'']; + name = "default_ip_remediation"; + on_success = "break"; + } + + { + decisions = [ + { + duration = "4h"; + type = "ban"; + } + ]; + + filters = [''Alert.Remediation == true && Alert.GetScope() == "Range"'']; + name = "default_ip_remediation"; + on_success = "break"; + } + ]; + }; + + crowdsec-firewall-bouncer = { + enable = true; + registerBouncer.enable = true; + createRulesets = true; + }; + + crowdsec-blocklist-import = { + enable = true; + allowListGithub = true; + }; + }; + + systemd.services = { + crowdsec = setupUnit; + crowdsec-setup = setupUnit; + + crowdsec-blocklist-import-frequent = { + after = [ + # keep-sorted start + "crowdsec-blocklist-gotify-proxy.service" + "crowdsec-firewall-bouncer-register.service" + "sops-install-secrets.service" + # keep-sorted end + ]; + wants = [ + # keep-sorted start + "crowdsec-blocklist-gotify-proxy.service" + "crowdsec-firewall-bouncer-register.service" + "sops-install-secrets.service" + # keep-sorted end + ]; + + serviceConfig.EnvironmentFile = templates."crowdsec-blocklist-import-env".path; + }; + + crowdsec-blocklist-import-limited = { + after = [ + # keep-sorted start + "crowdsec-blocklist-gotify-proxy.service" + "crowdsec-firewall-bouncer-register.service" + "sops-install-secrets.service" + # keep-sorted end + ]; + wants = [ + # keep-sorted start + "crowdsec-blocklist-gotify-proxy.service" + "crowdsec-firewall-bouncer-register.service" + "sops-install-secrets.service" + # keep-sorted end + ]; + + serviceConfig.EnvironmentFile = templates."crowdsec-blocklist-import-env".path; + }; + + crowdsec-blocklist-gotify-proxy = { + description = "Transform blocklist-import webhook payload for Gotify"; + + after = ["sops-install-secrets.service"]; + stopIfChanged = false; + wants = ["sops-install-secrets.service"]; + + wantedBy = ["crowdsec-blocklist-import-frequent.service" "crowdsec-blocklist-import-limited.service"]; + + serviceConfig = { + # keep-sorted start + DynamicUser = true; + ExecStart = "${getExe pkgs.socat} TCP-LISTEN:${proxyPort},bind=127.0.0.1,fork,reuseaddr SYSTEM:${getExe pkgs.crowdsec-blocklist-gotify-proxy}"; + LoadCredential = ["gotify_api_key:${secrets."crowdsec/gotify_api_key".path}"]; + Restart = "on-failure"; + StateDirectory = "crowdsec"; + StateDirectoryMode = "0750"; + SuccessExitStatus = [143]; + Type = "simple"; + # keep-sorted end + }; + }; + + # PostgreSQL local socket access. + crowdsec-firewall-bouncer-register.serviceConfig.RestrictAddressFamilies = mkForce ["AF_UNIX"]; + + crowdsec-traefik-bouncer = { + description = "Register Traefik CrowdSec bouncer"; + + # keep-sorted start block=yes newline_separated=yes + after = [ + # keep-sorted start + "crowdsec.service" + "sops-install-secrets.service" + # keep-sorted end + ]; + + before = ["traefik.service"]; + + wantedBy = ["traefik.service"]; + + wants = [ + # keep-sorted start + "crowdsec.service" + "sops-install-secrets.service" + # keep-sorted end + ]; + # keep-sorted end + + script = '' + set -eu + + attempt=1 + while [ "$attempt" -le 30 ]; do + if ${pkgs.crowdsec}/bin/cscli bouncers list | ${pkgs.gnugrep}/bin/grep -q "traefik-bouncer"; then + exit 0 + fi + + if ${pkgs.crowdsec}/bin/cscli bouncers add "traefik-bouncer" --key "$(${pkgs.coreutils}/bin/cat "$CREDENTIALS_DIRECTORY/traefik_bouncer_key")" >/dev/null; then + exit 0 + fi + + attempt=$((attempt + 1)) + ${pkgs.coreutils}/bin/sleep 2 + done + + ${pkgs.crowdsec}/bin/cscli bouncers list + exit 1 + ''; + + serviceConfig = { + # keep-sorted start + DynamicUser = true; + Group = config.services.crowdsec.group; + StateDirectory = "crowdsec"; + StateDirectoryMode = "0750"; + User = config.services.crowdsec.user; + # keep-sorted end + + # keep-sorted start + LoadCredential = ["traefik_bouncer_key:${secrets."traefik/crowdsec_bouncer_key".path}"]; + RemainAfterExit = true; + Type = "oneshot"; + # keep-sorted end + }; + }; + }; + }; +} diff --git a/modules/services/security/firewall.nix b/modules/services/security/firewall.nix new file mode 100644 --- /dev/null +++ b/modules/services/security/firewall.nix @@ -0,0 +1,5 @@ +{ + flake.modules.nixos.firewall = { + networking.nftables.enable = true; + }; +} diff --git a/modules/services/server/apprise.nix b/modules/services/server/apprise.nix new file mode 100644 --- /dev/null +++ b/modules/services/server/apprise.nix @@ -0,0 +1,50 @@ +{ + flake.modules.nixos.apprise = { + virtualisation.oci-containers.containers.apprise = { + hostname = "apprise"; + image = "caronc/apprise:latest"; + + environment = { + APPRISE_ADMIN = "y"; + APPRISE_STATEFUL_MODE = "simple"; + APPRISE_WORKER_COUNT = "1"; + APPRISE_WORKER_MAX_REQUESTS = "200"; + }; + + extraOptions = [ + "--network=host" + "--tmpfs=/tmp" + "--user=1000:1000" + + # Health check. + "--health-cmd=curl -fsSo /dev/null http://127.0.0.1:8000/ || exit 1" + "--health-interval=60s" + "--health-retries=5" + "--health-start-period=30s" + "--health-timeout=5s" + ]; + + volumes = [ + # keep-sorted start + "/var/lib/apprise/attach:/attach" + "/var/lib/apprise/config:/config" + "/var/lib/apprise/plugin:/plugin" + # keep-sorted end + ]; + }; + + networking.firewall.extraInputRules = '' + # Allow containers to reach host Apprise API. + iifname "podman*" tcp dport 8000 accept + ''; + + systemd.tmpfiles.rules = [ + # keep-sorted start + "d /var/lib/apprise 0750 1000 1000 -" + "d /var/lib/apprise/attach 0750 1000 1000 -" + "d /var/lib/apprise/config 0750 1000 1000 -" + "d /var/lib/apprise/plugin 0750 1000 1000 -" + # keep-sorted end + ]; + }; +} diff --git a/modules/services/server/cloudbeaver.nix b/modules/services/server/cloudbeaver.nix new file mode 100644 --- /dev/null +++ b/modules/services/server/cloudbeaver.nix @@ -0,0 +1,115 @@ +{ + flake.modules.nixos.cloudbeaver = { + # keep-sorted start + config, + lib, + pkgs, + vars, + # keep-sorted end + ... + }: let + inherit (lib) mkForce; + + secrets = config.sops.secrets; + templates = config.sops.templates; + + inherit (vars) groundDomain; + in { + sops = { + secrets."cloudbeaver/database_password" = {}; + + templates."cloudbeaver.env".content = '' + CLOUDBEAVER_DB_PASSWORD=${config.sops.placeholder."cloudbeaver/database_password"} + CLOUDBEAVER_QM_DB_PASSWORD=${config.sops.placeholder."cloudbeaver/database_password"} + ''; + }; + + virtualisation.oci-containers.containers.cloudbeaver = { + hostname = "cloudbeaver"; + image = "dbeaver/cloudbeaver:latest"; + + extraOptions = [ + "--network=host" + + # Health check. + "--health-cmd=curl -fsSo /dev/null http://127.0.0.1:8978/ || exit 1" + "--health-interval=60s" + "--health-retries=5" + "--health-start-period=30s" + "--health-timeout=5s" + ]; + + environment = { + CB_SERVER_URL = "https://cloudbeaver.${groundDomain}"; + CLOUDBEAVER_APP_FORWARD_PROXY = "true"; + CLOUDBEAVER_DB_BACKUP_ENABLED = "false"; + CLOUDBEAVER_DB_DRIVER = "postgres-jdbc"; + CLOUDBEAVER_DB_SCHEMA = "public"; + CLOUDBEAVER_DB_URL = "jdbc:postgresql://127.0.0.1:5432/cloudbeaver"; + CLOUDBEAVER_DB_USER = "cloudbeaver"; + CLOUDBEAVER_QM_DB_BACKUP_ENABLED = "false"; + CLOUDBEAVER_QM_DB_DRIVER = "postgres-jdbc"; + CLOUDBEAVER_QM_DB_SCHEMA = "public"; + CLOUDBEAVER_QM_DB_URL = "jdbc:postgresql://127.0.0.1:5432/cloudbeaver"; + CLOUDBEAVER_QM_DB_USER = "cloudbeaver"; + }; + + environmentFiles = [templates."cloudbeaver.env".path]; + volumes = ["/var/lib/cloudbeaver:/opt/cloudbeaver/workspace"]; + }; + + systemd = { + tmpfiles.rules = ["d /var/lib/cloudbeaver 0750 8978 8978 -"]; + + services = { + cloudbeaver-postgres-password = { + # keep-sorted start block=yes newline_separated=yes + after = [ + "postgresql.service" + "sops-install-secrets.service" + ]; + + before = ["podman-cloudbeaver.service"]; + + wantedBy = ["podman-cloudbeaver.service"]; + + wants = [ + "postgresql.service" + "sops-install-secrets.service" + ]; + # keep-sorted end + + script = '' + set -eu + password="$(${pkgs.coreutils}/bin/cat "$CREDENTIALS_DIRECTORY/database_password")" + ${config.services.postgresql.package}/bin/psql --dbname postgres --command "ALTER USER cloudbeaver WITH PASSWORD \$cloudbeaver\$''${password}\$cloudbeaver\$;" + ''; + + serviceConfig = { + # keep-sorted start + Group = "postgres"; + Type = "oneshot"; + User = "postgres"; + # keep-sorted end + + # keep-sorted start + LoadCredential = ["database_password:${secrets."cloudbeaver/database_password".path}"]; + RemainAfterExit = true; + # keep-sorted end + }; + }; + + podman-cloudbeaver = { + after = ["cloudbeaver-postgres-password.service"]; + stopIfChanged = false; + wants = ["cloudbeaver-postgres-password.service"]; + + serviceConfig = { + SuccessExitStatus = [143]; + TimeoutStopSec = mkForce "60s"; + }; + }; + }; + }; + }; +} diff --git a/modules/services/server/dockhand.nix b/modules/services/server/dockhand.nix new file mode 100644 --- /dev/null +++ b/modules/services/server/dockhand.nix @@ -0,0 +1,98 @@ +{ + flake.modules.nixos.dockhand = { + # keep-sorted start + config, + lib, + pkgs, + # keep-sorted end + ... + }: let + inherit (lib) mkForce; + + secrets = config.sops.secrets; + templates = config.sops.templates; + in { + sops = { + secrets."dockhand/database_password" = {}; + + templates."dockhand.env".content = '' + DATABASE_URL=postgres://dockhand:${config.sops.placeholder."dockhand/database_password"}@127.0.0.1:5432/dockhand + ''; + }; + + virtualisation.oci-containers.containers.dockhand = { + hostname = "dockhand"; + image = "fnsys/dockhand:latest"; + + extraOptions = [ + "--network=host" + + # Health check. + "--health-cmd=curl -fsSo /dev/null http://127.0.0.1:3000/api/health || exit 1" + "--health-interval=60s" + "--health-retries=5" + "--health-start-period=30s" + "--health-timeout=5s" + ]; + + environmentFiles = [templates."dockhand.env".path]; + volumes = ["/var/lib/dockhand:/app/data"]; + }; + + networking.firewall.interfaces.wg0.allowedTCPPorts = [3000]; + + systemd = { + tmpfiles.rules = ["d /var/lib/dockhand 0750 1001 1001 -"]; + + services = { + dockhand-postgres-password = { + # keep-sorted start block=yes newline_separated=yes + after = [ + "postgresql.service" + "sops-install-secrets.service" + ]; + + before = ["podman-dockhand.service"]; + + wantedBy = ["podman-dockhand.service"]; + + wants = [ + "postgresql.service" + "sops-install-secrets.service" + ]; + # keep-sorted end + + script = '' + set -eu + password="$(${pkgs.coreutils}/bin/cat "$CREDENTIALS_DIRECTORY/database_password")" + ${config.services.postgresql.package}/bin/psql --dbname postgres --command "ALTER USER dockhand WITH PASSWORD \$dockhand\$''${password}\$dockhand\$;" + ''; + + serviceConfig = { + # keep-sorted start + Group = "postgres"; + Type = "oneshot"; + User = "postgres"; + # keep-sorted end + + # keep-sorted start + LoadCredential = ["database_password:${secrets."dockhand/database_password".path}"]; + RemainAfterExit = true; + # keep-sorted end + }; + }; + + podman-dockhand = { + after = ["dockhand-postgres-password.service"]; + stopIfChanged = false; + wants = ["dockhand-postgres-password.service"]; + + serviceConfig = { + SuccessExitStatus = [143]; + TimeoutStopSec = mkForce "60s"; + }; + }; + }; + }; + }; +} diff --git a/modules/services/server/flaresolverr.nix b/modules/services/server/flaresolverr.nix new file mode 100644 --- /dev/null +++ b/modules/services/server/flaresolverr.nix @@ -0,0 +1,9 @@ +{ + flake.modules.nixos.flaresolverr = { + services.flaresolverr.enable = true; + + networking.firewall.extraInputRules = '' + iifname "podman*" tcp dport 8191 accept + ''; + }; +} diff --git a/modules/services/server/gotify.nix b/modules/services/server/gotify.nix new file mode 100644 --- /dev/null +++ b/modules/services/server/gotify.nix @@ -0,0 +1,133 @@ +{ + flake.modules.nixos.gotify-server = { + # keep-sorted start + config, + lib, + pkgs, + vars, + # keep-sorted end + ... + }: let + inherit (lib) mkForce; + + inherit (pkgs.nur.repos.adam0) gotifyPlugins; + + gotifyPluginsDrv = pkgs.symlinkJoin { + name = "gotify-plugins"; + paths = [gotifyPlugins.authentik]; + }; + + templates = config.sops.templates; + inherit (vars) groundDomain; + in { + sops = { + secrets = { + "gotify/client_id" = {}; + "gotify/client_secret" = {}; + }; + + templates."gotify.env".content = '' + GOTIFY_OIDC_CLIENTID=${config.sops.placeholder."gotify/client_id"} + GOTIFY_OIDC_CLIENTSECRET=${config.sops.placeholder."gotify/client_secret"} + ''; + }; + + services.gotify = { + enable = true; + package = pkgs.nur.repos.adam0.gotify-server; + stateDirectoryName = "gotify"; + + environment = { + GOTIFY_SERVER_PORT = 44407; + GOTIFY_SERVER_SECURECOOKIE = "true"; + + GOTIFY_OIDC_ENABLED = "true"; + GOTIFY_OIDC_ISSUER = "https://authentik.${groundDomain}/application/o/gotify/"; + GOTIFY_OIDC_LINK_BY_USERNAME = "true"; + GOTIFY_OIDC_REDIRECTURL = "https://gotify.${groundDomain}/auth/oidc/callback"; + + GOTIFY_DATABASE_DIALECT = "postgres"; + GOTIFY_DATABASE_CONNECTION = "host=/run/postgresql user=gotify dbname=gotify sslmode=disable"; + }; + + environmentFiles = [templates."gotify.env".path]; + }; + + users = { + groups.gotify = {}; + + users.gotify = { + group = "gotify"; + isSystemUser = true; + }; + }; + + networking.firewall.extraInputRules = '' + # Allow containers to reach host Gotify. + iifname "podman*" tcp dport 44407 accept + ''; + + systemd.services.gotify-server = { + after = [ + # keep-sorted start + "authentik-worker.service" + "authentik.service" + "postgresql.service" + "sops-install-secrets.service" + "systemd-tmpfiles-setup.service" + "traefik.service" + # keep-sorted end + ]; + + wants = [ + # keep-sorted start + "authentik-worker.service" + "authentik.service" + "postgresql.service" + "sops-install-secrets.service" + "systemd-tmpfiles-setup.service" + "traefik.service" + # keep-sorted end + ]; + + unitConfig = { + StartLimitBurst = 60; + StartLimitIntervalSec = "5min"; + }; + + serviceConfig = { + DynamicUser = mkForce false; + User = "gotify"; + Group = "gotify"; + RestartSec = "5s"; + }; + }; + + systemd.tmpfiles.rules = ["L+ /var/lib/gotify/data/plugins - - - - ${gotifyPluginsDrv}"]; + + systemd.services.gotify-optimize-images = { + description = "Optimize Gotify uploaded images"; + + after = ["gotify-server.service"]; + requires = ["gotify-server.service"]; + + serviceConfig = { + Type = "oneshot"; + User = "gotify"; + Group = "gotify"; + ExecStart = "${pkgs.gotify-optimize-images}/bin/gotify-optimize-images"; + }; + }; + + systemd.timers.gotify-optimize-images = { + description = "Daily Gotify image optimization"; + + wantedBy = ["timers.target"]; + + timerConfig = { + OnCalendar = "daily"; + Persistent = true; + }; + }; + }; +} diff --git a/modules/services/server/hawser.nix b/modules/services/server/hawser.nix new file mode 100644 --- /dev/null +++ b/modules/services/server/hawser.nix @@ -0,0 +1,69 @@ +{ + flake.modules.nixos.hawser = { + # keep-sorted start + config, + lib, + # keep-sorted end + ... + }: let + inherit + (lib) + # keep-sorted start + mkForce + mkOption + types + # keep-sorted end + ; + + hostname = config.networking.hostName; + hawserTokenSecret = "dockhand/hawser_tokens/${hostname}"; + in { + options.services.hawser.dockhandServerUrl = mkOption { + type = types.str; + default = "ws://10.100.0.1:3000/api/hawser/connect"; + description = "WebSocket URL for the dockhand server agent connection endpoint."; + }; + + config = { + sops.secrets.${hawserTokenSecret} = {}; + + sops.templates."hawser.env".content = '' + TOKEN=${config.sops.placeholder.${hawserTokenSecret}} + ''; + + virtualisation.oci-containers.containers.hawser = { + hostname = "hawser"; + image = "ghcr.io/finsys/hawser:latest"; + + extraOptions = [ + "--cgroupns=host" + "--network=host" + "--pid=host" + + # Health check. + "--health-cmd=wget -q --spider http://[::1]:2376/_hawser/health || exit 1" + "--health-interval=60s" + "--health-retries=5" + "--health-start-period=30s" + "--health-timeout=5s" + ]; + + environment = { + AGENT_NAME = hostname; + DOCKHAND_SERVER_URL = config.services.hawser.dockhandServerUrl; + }; + + environmentFiles = [config.sops.templates."hawser.env".path]; + volumes = [ + "/run/podman/podman.sock:/var/run/docker.sock" + "/var/lib/hawser:/data/stacks" + ]; + }; + + systemd = { + tmpfiles.rules = ["d /var/lib/hawser 0750 root root -"]; + services.podman-hawser.serviceConfig.TimeoutStopSec = mkForce "60s"; + }; + }; + }; +} diff --git a/modules/services/server/podman.nix b/modules/services/server/podman.nix new file mode 100644 --- /dev/null +++ b/modules/services/server/podman.nix @@ -0,0 +1,62 @@ +{ + flake.modules.nixos.podman = { + # keep-sorted start + config, + pkgs, + vars, + # keep-sorted end + ... + }: let + inherit (vars) username; + hostname = config.networking.hostName; + secret = config.sops.placeholder; + in { + virtualisation = { + podman = { + enable = true; + # Expose docker-compatible socket for tooling that expects dockerd. + dockerSocket.enable = true; + }; + + # Disable the podman compose warning about external command execution. + containers.containersConf.settings.engine.compose_warning_logs = false; + }; + + sops.templates."podman-dns.conf" = { + mode = "0444"; + content = '' + [containers] + dns_servers = [ + "${secret."dns/${hostname}/dns_1"}", + "${secret."dns/${hostname}/dns_2"}", + "${secret."dns/${hostname}/dns_3"}", + "${secret."dns/${hostname}/dns_4"}", + ] + ''; + }; + + systemd.services.podman-dns-conf = { + after = ["sops-install-secrets.service"]; + wantedBy = ["multi-user.target"]; + + serviceConfig = { + ExecStart = pkgs.writeShellScript "podman-dns-conf" '' + set -eu + mkdir -p /etc/containers/containers.conf.d + ${pkgs.gnused}/bin/sed 's/#.*"/"/' ${config.sops.templates."podman-dns.conf".path} > /etc/containers/containers.conf.d/00-dns.conf + ''; + RemainAfterExit = true; + Type = "oneshot"; + }; + }; + + environment.systemPackages = [pkgs.podman-compose]; + + networking.firewall.extraInputRules = '' + iifname "podman*" udp dport 53 accept + iifname "podman*" tcp dport 53 accept + ''; + + users.users.${username}.extraGroups = ["podman"]; + }; +} diff --git a/modules/services/server/traefik.nix b/modules/services/server/traefik.nix new file mode 100644 --- /dev/null +++ b/modules/services/server/traefik.nix @@ -0,0 +1,284 @@ +{ + flake.modules.nixos.traefik = { + # keep-sorted start + config, + vars, + # keep-sorted end + ... + }: let + secrets = config.sops.secrets; + templates = config.sops.templates; + + inherit (vars) groundDomain; + in { + sops = { + secrets = { + # keep-sorted start block=yes newline_separated=yes + "traefik/crowdsec_bouncer_key" = { + owner = "traefik"; + mode = "0400"; + }; + + "traefik/mail" = {}; + + "traefik/porkbun_api_key" = {}; + + "traefik/porkbun_secret_api_key" = {}; + # keep-sorted end + }; + + templates."traefik.env".content = '' + TRAEFIK_ACME_EMAIL=${config.sops.placeholder."traefik/mail"} + PORKBUN_API_KEY=${config.sops.placeholder."traefik/porkbun_api_key"} + PORKBUN_SECRET_API_KEY=${config.sops.placeholder."traefik/porkbun_secret_api_key"} + ''; + }; + + services = { + # keep-sorted start block=yes newline_separated=yes + traefik = { + enable = true; + group = "podman"; + environmentFiles = [templates."traefik.env".path]; + + staticConfigOptions = { + # keep-sorted start block=yes newline_separated=yes + accessLog.filePath = "/var/log/traefik/access.log"; + + api = { + dashboard = true; + insecure = false; + }; + + certificatesResolvers.myresolver.acme = { + dnsChallenge.provider = "porkbun"; + email = "\${TRAEFIK_ACME_EMAIL}"; + storage = "/var/lib/traefik/acme.json"; + }; + + entryPoints = { + # keep-sorted start block=yes newline_separated=yes + ssh.address = ":22"; + + web = { + address = ":80"; + + http = { + middlewares = ["crowdsec@file"]; + + redirections.entryPoint = { + to = "websecure"; + scheme = "https"; + }; + }; + }; + + websecure = { + address = ":443"; + + http = { + tls = { + certResolver = "myresolver"; + + domains = [ + { + main = "${groundDomain}"; + sans = ["*.${groundDomain}"]; + } + ]; + }; + + middlewares = ["crowdsec@file"]; + }; + + transport.respondingTimeouts = { + readTimeout = "600s"; + writeTimeout = "600s"; + idleTimeout = "600s"; + }; + }; + # keep-sorted end + }; + + experimental.plugins.bouncer = { + moduleName = "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"; + version = "v1.6.0"; + }; + + log = { + level = "INFO"; + filePath = "/var/log/traefik/traefik.log"; + }; + + providers.docker = { + endpoint = "unix:///run/podman/podman.sock"; + exposedByDefault = false; + network = "network"; + }; + # keep-sorted end + }; + + dynamicConfigOptions = { + http = { + middlewares = { + # keep-sorted start block=yes newline_separated=yes + authentik-proxy.forwardAuth = { + address = "http://[::1]:9005/outpost.goauthentik.io/auth/traefik"; + trustForwardHeader = true; + }; + + authentik.forwardAuth = { + address = "http://[::1]:9005/outpost.goauthentik.io/auth/traefik"; + trustForwardHeader = true; + authResponseHeaders = [ + # keep-sorted start + "X-authentik-email" + "X-authentik-entitlements" + "X-authentik-groups" + "X-authentik-jwt" + "X-authentik-meta-app" + "X-authentik-meta-jwks" + "X-authentik-meta-outpost" + "X-authentik-meta-provider" + "X-authentik-meta-version" + "X-authentik-name" + "X-authentik-uid" + "X-authentik-username" + # keep-sorted end + ]; + }; + + crowdsec.plugin.bouncer = { + enabled = true; + crowdsecMode = "appsec"; + crowdsecAppsecEnabled = true; + crowdsecAppsecHost = "127.0.0.1:7424"; + crowdsecAppsecKeyFile = secrets."traefik/crowdsec_bouncer_key".path; + crowdsecLapiUrl = "http://127.0.0.1:8080"; + crowdsecLapiKeyFile = secrets."traefik/crowdsec_bouncer_key".path; + }; + + redirect-to-https.redirectscheme = { + scheme = "https"; + permanent = true; + }; + # keep-sorted end + }; + + routers = { + # keep-sorted start block=yes newline_separated=yes + apprise = { + entryPoints = ["websecure"]; + middlewares = ["authentik@file"]; + rule = "Host(`apprise.${groundDomain}`)"; + service = "apprise"; + }; + + authentik = { + entryPoints = ["websecure"]; + rule = "Host(`authentik.${groundDomain}`)"; + service = "authentik"; + }; + + authentik-outpost = { + entryPoints = ["websecure"]; + priority = 15; + rule = "Host(`traefik.${groundDomain}`) && PathPrefix(`/outpost.goauthentik.io/`)"; + service = "authentik-outpost"; + }; + + cloudbeaver = { + entryPoints = ["websecure"]; + middlewares = ["authentik@file"]; + rule = "Host(`cloudbeaver.${groundDomain}`)"; + service = "cloudbeaver"; + }; + + dockhand = { + entryPoints = ["websecure"]; + rule = "Host(`dockhand.${groundDomain}`)"; + service = "dockhand"; + }; + + gotify = { + entryPoints = ["websecure"]; + rule = "Host(`gotify.${groundDomain}`)"; + service = "gotify"; + }; + + traefik-dashboard = { + entryPoints = ["websecure"]; + middlewares = ["authentik@file"]; + rule = "Host(`traefik.${groundDomain}`)"; + service = "api@internal"; + }; + # keep-sorted end + }; + + services = { + # keep-sorted start block=yes newline_separated=yes + apprise.loadBalancer.servers = [ + {url = "http://127.0.0.1:8000";} + ]; + + authentik-outpost.loadBalancer.servers = [ + {url = "http://[::1]:9005/outpost.goauthentik.io";} + ]; + + authentik.loadBalancer.servers = [ + {url = "http://[::1]:9000";} + ]; + + cloudbeaver.loadBalancer.servers = [ + {url = "http://127.0.0.1:8978";} + ]; + + dockhand.loadBalancer.servers = [ + {url = "http://127.0.0.1:3000";} + ]; + + gotify.loadBalancer.servers = [ + {url = "http://127.0.0.1:44407";} + ]; + # keep-sorted end + }; + }; + + tcp = { + routers.ssh-euclid = { + entryPoints = ["ssh"]; + rule = "HostSNI(`euclid.${groundDomain}`)"; + service = "ssh-euclid"; + tls.certResolver = "myresolver"; + }; + + services.ssh-euclid.loadBalancer.servers = [ + {address = "[::1]:2222";} + ]; + }; + }; + }; + # keep-sorted end + }; + + networking.firewall.allowedTCPPorts = [ + # keep-sorted start numeric=yes + 22 + 80 + 443 + # keep-sorted end + ]; + + systemd = { + tmpfiles.rules = ["d /var/log/traefik 0750 traefik traefik -"]; + + services.traefik = { + wants = ["podman.socket"]; + after = ["podman.socket"]; + + serviceConfig.TimeoutStopSec = "60s"; + stopIfChanged = false; + }; + }; + }; +} diff --git a/modules/desktop/hyprland/keybinds/screenshots-and-color.nix b/modules/desktop/hyprland/keybinds/screenshots-and-color.nix --- a/modules/desktop/hyprland/keybinds/screenshots-and-color.nix +++ b/modules/desktop/hyprland/keybinds/screenshots-and-color.nix @@ -13,7 +13,7 @@ # keep-sorted start hyprpicker = getExe pkgs.hyprpicker; hyprshot = getExe config.programs.hyprshot.package; - screenshotDir = "${config.xdg.userDirs.pictures}/Screenshots"; + screenshotDir = "${config.xdg.userDirs.pictures}/screenshots"; # keep-sorted end in { config.programs.hylix.bindGroups = [ diff --git a/modules/programs/cli/starship/format.nix b/modules/programs/cli/starship/format.nix --- a/modules/programs/cli/starship/format.nix +++ b/modules/programs/cli/starship/format.nix @@ -28,14 +28,7 @@ "$jobs" "$fossil_branch" - "[ ](#00000000)" - "[ ](bg:base01)" - "$git_branch" - "[ ](bg:base01)" - "$git_commit" - "$git_state" - "$git_status" - "[ ](bg:base01)" + "\${custom.jj}" "$package" diff --git a/modules/programs/cli/starship/git.nix b/modules/programs/cli/starship/git.nix deleted file mode 100644 --- a/modules/programs/cli/starship/git.nix +++ /dev/null @@ -1,47 +0,0 @@ -{ - flake.modules.homeManager.starship = { - programs.starship.settings = { - # keep-sorted start block=yes newline_separated=yes - fossil_branch = { - format = "[$symbol$branch]($style) "; - symbol = " "; - style = "fg:magenta bold"; - }; - - git_branch = { - format = "[$symbol$branch(:$remote_branch)]($style)"; - symbol = " "; - style = "bg:base01 fg:magenta bold"; - }; - - git_commit = { - format = "[\\($hash$tag\\)]($style)"; - style = "bg:base01 fg:green bold"; - tag_disabled = false; - only_detached = false; - tag_symbol = "  "; - }; - - git_state = { - format = "[ $state( $progress_current/$progress_total)]($style)"; - style = "bg:base01 fg:yellow bold"; - # keep-sorted start - am = "am"; - am_or_rebase = "am/rebase"; - bisect = "bisecting"; - cherry_pick = "cherry-picking"; - merge = "merging"; - rebase = "rebasing"; - revert = "reverting"; - # keep-sorted end - }; - - git_status = { - format = "([ \\[$all_status$ahead_behind\\]]($style))"; - style = "bg:base01 fg:base08 bold"; - deleted = ""; - }; - # keep-sorted end - }; - }; -} diff --git a/modules/programs/cli/starship/jujutsu.nix b/modules/programs/cli/starship/jujutsu.nix new file mode 100644 --- /dev/null +++ b/modules/programs/cli/starship/jujutsu.nix @@ -0,0 +1,168 @@ +{ + flake.modules.homeManager.starship = { + # keep-sorted start + config, + lib, + pkgs, + # keep-sorted end + ... + }: let + inherit (lib) getExe; + inherit (lib.self) starshipJjTrueColor; + inherit + (builtins.mapAttrs (_: starshipJjTrueColor) config.lib.stylix.colors.withHashtag) + # keep-sorted start + base01 + base08 + base0A + base0B + base0E + # keep-sorted end + ; + + starshipJjConfig = (pkgs.formats.toml {}).generate "starship-jj.toml" { + bookmarks = { + exclude = []; + search_depth = 100; + }; + + module_separator = " "; + reset_color = false; + + module = [ + { + type = "Symbol"; + symbol = " "; + color = base0E; + bg_color = base01; + bold = true; + } + + { + type = "Bookmarks"; + separator = " "; + color = base0E; + bg_color = base01; + bold = true; + behind_symbol = "⇡"; + ignore_empty_commits = "None"; + max_bookmarks = 1; + surround_with_quotes = false; + } + + { + type = "Commit"; + color = base0B; + bg_color = base01; + bold = true; + + change = { + color = base0B; + bg_color = base01; + bold = true; + }; + + commit = { + color = base0B; + bg_color = base01; + bold = true; + }; + + empty_text = "(no description set)"; + max_length = 24; + previous_message_symbol = "⇣"; + show_previous_if_empty = false; + surround_with_quotes = false; + } + + { + type = "State"; + separator = " "; + + conflict = { + text = "conflict"; + color = base0A; + bg_color = base01; + bold = true; + }; + + divergent = { + text = "divergent"; + color = base0A; + bg_color = base01; + bold = true; + }; + + empty = { + text = "empty"; + color = base0A; + bg_color = base01; + bold = true; + }; + + hidden = { + text = "hidden"; + color = base0A; + bg_color = base01; + bold = true; + }; + + immutable = { + text = "immutable"; + color = base0A; + bg_color = base01; + bold = true; + }; + } + + { + type = "Metrics"; + color = base08; + bg_color = base01; + bold = true; + hide_if_empty = true; + template = "[{changed} {added}{removed}]"; + + changed_files = { + color = base08; + bg_color = base01; + bold = true; + }; + + added_lines = { + prefix = "+"; + color = base08; + bg_color = base01; + bold = true; + }; + + removed_lines = { + prefix = "-"; + color = base08; + bg_color = base01; + bold = true; + }; + } + ]; + }; + in { + programs.starship.settings = { + # keep-sorted start block=yes newline_separated=yes + custom.jj = { + command = "${getExe pkgs.starship-jj} --ignore-working-copy starship prompt --starship-config ${starshipJjConfig}"; + format = "[ ](#00000000)[ ](bg:base01)[$output]($style)[ ](bg:base01)"; + ignore_timeout = true; + style = "bg:base01 fg:base0E bold"; + use_stdin = false; + when = "${getExe pkgs.starship-jj} root --quiet"; + }; + + fossil_branch = { + format = "[$symbol$branch]($style) "; + symbol = " "; + style = "fg:base0E bold"; + }; + # keep-sorted end + }; + }; +} diff --git a/modules/programs/gui/ghostty/settings.nix b/modules/programs/gui/ghostty/settings.nix --- a/modules/programs/gui/ghostty/settings.nix +++ b/modules/programs/gui/ghostty/settings.nix @@ -5,33 +5,43 @@ font-feature = "-calt, -liga, -dlig"; # Cursor settings. + # keep-sorted start cursor-style = "block"; cursor-style-blink = true; shell-integration-features = "no-cursor,ssh-terminfo,ssh-env"; + # keep-sorted end # Appearance settings. - window-theme = "system"; - window-decoration = "none"; + # keep-sorted start gtk-titlebar = false; + resize-overlay = "never"; + window-decoration = "none"; window-padding-x = 2; window-padding-y = 2; - resize-overlay = "never"; + window-theme = "system"; + # keep-sorted end # Misc settings. - copy-on-select = false; + # keep-sorted start auto-update = "off"; confirm-close-surface = false; + copy-on-select = false; right-click-action = "ignore"; + # keep-sorted end # Improve startup time. - quit-after-last-window-closed = false; + # keep-sorted start gtk-single-instance = true; + quit-after-last-window-closed = false; + # keep-sorted end # Disable directory inherit. - working-directory = "home"; - window-inherit-working-directory = false; - tab-inherit-working-directory = false; + # keep-sorted start split-inherit-working-directory = false; + tab-inherit-working-directory = false; + window-inherit-working-directory = false; + working-directory = "home"; + # keep-sorted end }; }; } diff --git a/modules/programs/gui/zen/search.nix b/modules/programs/gui/zen/search.nix --- a/modules/programs/gui/zen/search.nix +++ b/modules/programs/gui/zen/search.nix @@ -60,42 +60,15 @@ }; # Disabled (hidden). + # keep-sorted start bing.metaData.hidden = true; ddg.metaData.hidden = true; - qwant.metaData.hidden = true; ecosia.metaData.hidden = true; + qwant.metaData.hidden = true; + # keep-sorted end # Nix related search engines. - nix = { - urls = [ - { - template = "https://searchix.ovh/"; - params = [ - { - name = "query"; - value = "{searchTerms}"; - } - ]; - } - ]; - icon = nixIcon; - definedAliases = ["@n"]; - }; - nixos = { - urls = [ - { - template = "https://searchix.ovh/options/nixos/search"; - params = [ - { - name = "query"; - value = "{searchTerms}"; - } - ]; - } - ]; - icon = nixIcon; - definedAliases = ["@no"]; - }; + # keep-sorted start block=yes "home-manager" = { urls = [ { @@ -126,6 +99,36 @@ icon = nixIcon; definedAliases = ["@np"]; }; + nix = { + urls = [ + { + template = "https://searchix.ovh/"; + params = [ + { + name = "query"; + value = "{searchTerms}"; + } + ]; + } + ]; + icon = nixIcon; + definedAliases = ["@n"]; + }; + nixos = { + urls = [ + { + template = "https://searchix.ovh/options/nixos/search"; + params = [ + { + name = "query"; + value = "{searchTerms}"; + } + ]; + } + ]; + icon = nixIcon; + definedAliases = ["@no"]; + }; nur = { urls = [ { @@ -141,23 +144,10 @@ icon = nixIcon; definedAliases = ["@nu"]; }; + # keep-sorted end # Wiki. - "nixos-wiki" = { - urls = [ - { - template = "https://wiki.nixos.org/w/index.php"; - params = [ - { - name = "search"; - value = "{searchTerms}"; - } - ]; - } - ]; - icon = nixIcon; - definedAliases = ["@nw"]; - }; + # keep-sorted start block=yes "arch-wiki" = { urls = [ { @@ -188,8 +178,25 @@ iconMapObj."16" = "https://minecraft.wiki/favicon.ico"; definedAliases = ["@mw"]; }; + "nixos-wiki" = { + urls = [ + { + template = "https://wiki.nixos.org/w/index.php"; + params = [ + { + name = "search"; + value = "{searchTerms}"; + } + ]; + } + ]; + icon = nixIcon; + definedAliases = ["@nw"]; + }; + # keep-sorted end # Development resources. + # keep-sorted start block=yes crates = { urls = [ { @@ -239,6 +246,7 @@ iconMapObj."16" = "https://github.com/favicon.ico"; definedAliases = ["@gh"]; }; + # keep-sorted end }; }; }; diff --git a/modules/programs/tui/neovim/components/dashboard.nix b/modules/programs/tui/neovim/components/dashboard.nix --- a/modules/programs/tui/neovim/components/dashboard.nix +++ b/modules/programs/tui/neovim/components/dashboard.nix @@ -317,16 +317,22 @@ { pane = 2; icon = ""; - title = "Git Status"; + title = "VCS Status"; section = "terminal"; cmd = getExe (pkgs.writeShellApplication { - name = "dashboard-git-status"; + name = "dashboard-vcs-status"; runtimeInputs = with pkgs; [ + # keep-sorted start gawk git + jujutsu + # keep-sorted end ]; text = '' - if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + if jj root >/dev/null 2>&1; then + jj --color always status \ + | awk 'NR <= 6 { print; seen = 1 } END { if (!seen) print "No jj changes" }' + elif git rev-parse --is-inside-work-tree >/dev/null 2>&1; then git -c color.status=always status --short --branch --renames \ | awk 'NR <= 6 { print; seen = 1 } END { if (!seen) print "No git changes" }' else -- tangled.sh