From eab9e8b58d5f180935066440305cb8024470cbbf Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Thu, 27 Nov 2025 11:15:49 +0100 Subject: [PATCH 001/108] trying some stuff --- .../services/persistance/convex/default.nix | 21 +++ .../services/persistance/convex/source.nix | 149 ++++++++++++++++++ packages/convex/default.nix | 59 +++++++ systems/x86_64-linux/ulmo/default.nix | 2 + 4 files changed, 231 insertions(+) create mode 100644 modules/nixos/services/persistance/convex/default.nix create mode 100644 modules/nixos/services/persistance/convex/source.nix create mode 100644 packages/convex/default.nix diff --git a/modules/nixos/services/persistance/convex/default.nix b/modules/nixos/services/persistance/convex/default.nix new file mode 100644 index 0000000..3e01c59 --- /dev/null +++ b/modules/nixos/services/persistance/convex/default.nix @@ -0,0 +1,21 @@ +{ config, pkgs, lib, namespace, ... }: +let + inherit (lib) mkIf mkEnableOption; + + cfg = config.${namespace}.services.persistance.convex; +in +{ + imports = [ ./source.nix ]; + + options.${namespace}.services.persistance.convex = { + enable = mkEnableOption "enable Convex"; + }; + + config = mkIf cfg.enable { + services.convex = { + enable = true; + package = pkgs.${namespace}.convex; + secret = "ThisIsMyAwesomeSecret"; + }; + }; +} diff --git a/modules/nixos/services/persistance/convex/source.nix b/modules/nixos/services/persistance/convex/source.nix new file mode 100644 index 0000000..c56e3ab --- /dev/null +++ b/modules/nixos/services/persistance/convex/source.nix @@ -0,0 +1,149 @@ +{ config, pkgs, lib, namespace, ... }: +let + inherit (lib) mkIf mkEnableOption mkPackageOption mkOption optional types; + + cfg = config.services.convex; + + default_user = "convex"; + default_group = "convex"; +in +{ + options.services.convex = { + enable = mkEnableOption "enable Convex (backend only for now)"; + + package = mkPackageOption pkgs "convex" {}; + + name = lib.mkOption { + type = types.str; + default = "convex"; + description = '' + Name for the instance. + ''; + }; + + secret = lib.mkOption { + type = types.str; + default = ""; + description = '' + Secret for the instance. + ''; + }; + + apiPort = mkOption { + type = types.port; + default = 3210; + description = '' + The TCP port to use for the API. + ''; + }; + + actionsPort = mkOption { + type = types.port; + default = 3211; + description = '' + The TCP port to use for the HTTP actions. + ''; + }; + + dashboardPort = mkOption { + type = types.port; + default = 6791; + description = '' + The TCP port to use for the Dashboard. + ''; + }; + + openFirewall = lib.mkOption { + type = types.bool; + default = false; + description = '' + Whether to open ports in the firewall for the server. + ''; + }; + + user = lib.mkOption { + type = types.str; + default = default_user; + description = '' + As which user to run the service. + ''; + }; + + group = lib.mkOption { + type = types.str; + default = default_group; + description = '' + As which group to run the service. + ''; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = cfg.secret != ""; + message = '' + No secret provided for convex + ''; + } + ]; + + users = { + users.${cfg.user} = { + description = "System user for convex service"; + isSystemUser = true; + group = cfg.group; + }; + + groups.${cfg.group} = {}; + }; + + networking.firewall.allowedTCPPorts = optional cfg.openFirewall [ cfg.apiPort cfg.actionsPort cfg.dashboardPort ]; + + environment.systemPackages = [ cfg.package ]; + + systemd.services.convex = { + description = "Convex Backend server"; + + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + serviceConfig = { + ExecStart = "${cfg.package}/bin --instance-name ${cfg.name} --instance-secret ${cfg.secret}"; + Type = "notify"; + + User = cfg.user; + Group = cfg.group; + + RuntimeDirectory = "convex"; + RuntimeDirectoryMode = "0775"; + StateDirectory = "convex"; + StateDirectoryMode = "0775"; + Umask = "0077"; + + CapabilityBoundingSet = ""; + NoNewPrivileges = true; + + # Sandboxing + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + PrivateDevices = true; + PrivateUsers = true; + ProtectClock = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectControlGroups = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_UNIX" + ]; + RestrictNamespaces = true; + LockPersonality = true; + }; + }; + }; +} diff --git a/packages/convex/default.nix b/packages/convex/default.nix new file mode 100644 index 0000000..9dab056 --- /dev/null +++ b/packages/convex/default.nix @@ -0,0 +1,59 @@ +{ + lib, + stdenv, + rustPlatform, + fetchFromGitHub, + + # dependencies + openssl, + pkg-config, + cmake, + llvmPackages, + postgresql, + sqlite, + + #options + dbBackend ? "postgresql", + + ... +}: +rustPlatform.buildRustPackage rec { + pname = "convex"; + version = "2025-08-20-c9b561e"; + + src = fetchFromGitHub { + owner = "get-convex"; + repo = "convex-backend"; + rev = "c9b561e1b365c85ef28af35d742cb7dd174b5555"; + hash = "sha256-4h4AQt+rQ+nTw6eTbbB5vqFt9MFjKYw3Z7bGXdXijJ0="; + }; + + cargoHash = "sha256-pcDNWGrk9D0qcF479QAglPLFDZp27f8RueP5/lq9jho="; + + cargoBuildFlags = [ + "-p" "local_backend" + "--bin" "convex-local-backend" + ]; + + env = { + LIBCLANG_PATH = "${llvmPackages.libclang}/lib"; + }; + + strictDeps = true; + + # Build-time dependencies + nativeBuildInputs = [ pkg-config cmake rustPlatform.bindgenHook ]; + + # Run-time dependencies + buildInputs = + [ openssl ] + ++ lib.optional (dbBackend == "sqlite") sqlite + ++ lib.optional (dbBackend == "postgresql") postgresql; + + buildFeatures = ""; + + meta = with lib; { + license = licenses.fsl11Asl20; + mainProgram = "convex"; + }; +} \ No newline at end of file diff --git a/systems/x86_64-linux/ulmo/default.nix b/systems/x86_64-linux/ulmo/default.nix index 9d12de8..e8602b5 100644 --- a/systems/x86_64-linux/ulmo/default.nix +++ b/systems/x86_64-linux/ulmo/default.nix @@ -206,6 +206,8 @@ # uptime-kuma.enable = true; }; + persistance.convex.enable = true; + security.vaultwarden = { enable = true; database = { From 3730ab856ba609e381a3c35bac4d47c40dd27d8d Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Mon, 8 Dec 2025 16:31:52 +0100 Subject: [PATCH 002/108] feat: improve justfiles --- .just/machine.just | 13 ++++++++----- .just/vars.just | 15 +++++---------- .justfile | 45 ++++++++++++++++++++++++++------------------- 3 files changed, 39 insertions(+), 34 deletions(-) diff --git a/.just/machine.just b/.just/machine.just index ca10e1c..3e3ba14 100644 --- a/.just/machine.just +++ b/.just/machine.just @@ -1,11 +1,14 @@ -@_default: list +set unstable := true +set quiet := true + +_default: list [doc('List machines')] -@list: +list: ls -1 ../systems/x86_64-linux/ [doc('Update the target machine')] [no-exit-message] -@update machine: - just assert '-d "../systems/x86_64-linux/{{ machine }}"' "Machine {{ machine }} does not exist, must be one of: $(ls ../systems/x86_64-linux/ | sed ':a;N;$!ba;s/\n/, /g')" - nixos-rebuild switch -L --use-remote-sudo --target-host {{ machine }} --flake ..#{{ machine }} +update machine: + just assert '-d "../systems/x86_64-linux/{{ machine }}"' "Machine {{ machine }} does not exist, must be one of: $(ls ../systems/x86_64-linux/ | tr '\n' ' ')" + nixos-rebuild switch --use-remote-sudo --target-host {{ machine }} --flake ..#{{ machine }} diff --git a/.just/vars.just b/.just/vars.just index 3b706da..230f00c 100644 --- a/.just/vars.just +++ b/.just/vars.just @@ -1,21 +1,16 @@ set unstable := true +set quiet := true base_path := invocation_directory() / "systems/x86_64-linux" -# sops := "nix shell nixpkgs#sops --command sops" -# yq := "nix shell nixpkgs#yq --command yq" - -sops := "sops" -yq := "yq" - -@_default: +_default: just --list [doc('list all vars of the target machine')] list machine: sops decrypt {{ base_path }}/{{ machine }}/secrets.yml -@edit machine: +edit machine: sops edit {{ base_path }}/{{ machine }}/secrets.yml @set machine key value: @@ -26,10 +21,10 @@ list machine: echo "Done" -@get machine key: +get machine key: sops decrypt {{ base_path }}/{{ machine }}/secrets.yml | yq ".$(echo "{{ key }}" | sed -E 's/\//./g')" -@remove machine key: +remove machine key: sops unset {{ base_path }}/{{ machine }}/secrets.yml "$(printf '%s\n' '["{{ key }}"]' | sed -E 's#/#"]["#g; s/\["([0-9]+)"\]/[\1]/g')" git add {{ base_path }}/{{ machine }}/secrets.yml diff --git a/.justfile b/.justfile index 75537e1..1937f04 100644 --- a/.justfile +++ b/.justfile @@ -1,33 +1,40 @@ -@_default: - just --list --list-submodules +_default: + just --list --list-submodules + +set unstable +set quiet -[doc('Manage vars')] mod vars '.just/vars.just' - -[doc('Manage machines')] mod machine '.just/machine.just' [doc('Show information about project')] -@show: - echo "show" +show: + echo "show" [doc('update the flake dependencies')] -@update: - nix flake update - git commit -m 'chore: update dependencies' -- ./flake.lock > /dev/null - echo "Done" +update: + nix flake update + git commit -m 'chore: update dependencies' -- ./flake.lock > /dev/null + echo "Done" + +[doc('Rebase branch on main')] +rebase: + git stash -q \ + && git fetch \ + && git rebase origin/main \ + && git stash pop -q + + echo "Done" [doc('Introspection on flake output')] -@select key: - nix eval --show-trace --json .#{{ key }} | jq . - - +select key: + nix eval --json .#{{ key }} | jq . #=============================================================================================== # Utils -#=============================================================================================== -[no-exit-message] +# =============================================================================================== [no-cd] +[no-exit-message] [private] -@assert condition message: - [ {{ condition }} ] || { echo -e 1>&2 "\n\x1b[1;41m Error \x1b[0m {{ message }}\n"; exit 1; } +assert condition message: + [ {{ condition }} ] || { echo -e 1>&2 "\n\x1b[1;41m Error \x1b[0m {{ message }}\n"; exit 1; } From e849826de61f2bf2c5ba8bad4412b92c790efd97 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Mon, 8 Dec 2025 16:32:45 +0100 Subject: [PATCH 003/108] chore: update dependencies --- flake.lock | 126 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 87 insertions(+), 39 deletions(-) diff --git a/flake.lock b/flake.lock index adfa1cf..07a2120 100644 --- a/flake.lock +++ b/flake.lock @@ -84,11 +84,19 @@ "treefmt-nix": "treefmt-nix" }, "locked": { +<<<<<<< HEAD "lastModified": 1765033957, "narHash": "sha256-yL5IjUOne+h6AodxxqoqwPgRy2HXle6+W4Aa2GVJruk=", "rev": "9985ce76af367e7c9e3022c5b893418059a17491", "type": "tarball", "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/9985ce76af367e7c9e3022c5b893418059a17491.tar.gz" +======= + "lastModified": 1764220269, + "narHash": "sha256-rSSmhTCjfZLZog3qO6Q5C58pINmDv8EheGUhcojxd6c=", + "rev": "c70c04d09477ceee5820a8da4d9c0d1b50eb6cc6", + "type": "tarball", + "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/c70c04d09477ceee5820a8da4d9c0d1b50eb6cc6.tar.gz" +>>>>>>> 122a796 (chore: update dependencies) }, "original": { "type": "tarball", @@ -130,11 +138,19 @@ ] }, "locked": { +<<<<<<< HEAD "lastModified": 1764627417, "narHash": "sha256-D6xc3Rl8Ab6wucJWdvjNsGYGSxNjQHzRc2EZ6eeQ6l4=", "owner": "nix-community", "repo": "disko", "rev": "5a88a6eceb8fd732b983e72b732f6f4b8269bef3", +======= + "lastModified": 1764110879, + "narHash": "sha256-xanUzIb0tf3kJ+PoOFmXEXV1jM3PjkDT/TQ5DYeNYRc=", + "owner": "nix-community", + "repo": "disko", + "rev": "aecba248f9a7d68c5d1ed15de2d1c8a4c994a3c5", +>>>>>>> 122a796 (chore: update dependencies) "type": "github" }, "original": { @@ -149,11 +165,19 @@ "nixpkgs": "nixpkgs" }, "locked": { +<<<<<<< HEAD "lastModified": 1764775116, "narHash": "sha256-S4fY3fytcqXBuOSbQjEVke2eqK9/e/6Jy3jp0JGM2X4=", "owner": "emmanuelrosa", "repo": "erosanix", "rev": "172661ccc78b1529a294eee5e99ca1616c934f37", +======= + "lastModified": 1763851335, + "narHash": "sha256-mmDc9dREBGGZW1iCB3AbMLBzsXrf48hJ+EzJ6g7Tdbk=", + "owner": "emmanuelrosa", + "repo": "erosanix", + "rev": "17407369c38ac2ade3be648666d30f6469908bdb", +>>>>>>> 122a796 (chore: update dependencies) "type": "github" }, "original": { @@ -170,11 +194,19 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { +<<<<<<< HEAD "lastModified": 1764915802, "narHash": "sha256-eHTucU43sRCpvvTt5eey9htcWipS7ZN3B7ts6MiXLxo=", "owner": "nix-community", "repo": "fenix", "rev": "a83a78fd3587d9f3388f0b459ad9c2bbd6d1b6d8", +======= + "lastModified": 1764226020, + "narHash": "sha256-FzUCFwXNjLnnZmVqYj/FjlBhUpat59SExflEaIGT62s=", + "owner": "nix-community", + "repo": "fenix", + "rev": "2d8176c02f7be6d13578d24d5fd5049f1b46a4c5", +>>>>>>> 122a796 (chore: update dependencies) "type": "github" }, "original": { @@ -190,11 +222,19 @@ "nixpkgs": "nixpkgs_2" }, "locked": { +<<<<<<< HEAD "lastModified": 1765024561, "narHash": "sha256-xtfg5gNfyiyBTfWwbKgatV1sPeJjEnUczHCaSWi+crY=", "owner": "nix-community", "repo": "flake-firefox-nightly", "rev": "e6f559729459a7890f01b258c33c1025800f5dbb", +======= + "lastModified": 1764242161, + "narHash": "sha256-Yxeu6Zm85RwER/0z0fv3mX2xaBy38PZKgdAAE57huRU=", + "owner": "nix-community", + "repo": "flake-firefox-nightly", + "rev": "ca10e2ff1ec58b1a3722ccb3c052c57c5e070780", +>>>>>>> 122a796 (chore: update dependencies) "type": "github" }, "original": { @@ -574,11 +614,19 @@ "rust-overlay": "rust-overlay" }, "locked": { +<<<<<<< HEAD "lastModified": 1764617621, "narHash": "sha256-Eq0TvWs6xhKZs5HXH1hlrNasrHD7AOEdeLkTis//X7w=", "owner": "himmelblau-idm", "repo": "himmelblau", "rev": "c19494250d8c15e7c75e9301bdc271579a6dc77a", +======= + "lastModified": 1764184347, + "narHash": "sha256-xhzCn/rnBDTybHtuFV2IhCgjLMsCVpbzpEL0w//4Na8=", + "owner": "himmelblau-idm", + "repo": "himmelblau", + "rev": "9f0f6e27b6a9acdb12c4807cc1402132b21009f3", +>>>>>>> 122a796 (chore: update dependencies) "type": "github" }, "original": { @@ -594,11 +642,11 @@ ] }, "locked": { - "lastModified": 1764603455, - "narHash": "sha256-Q70rxlbrxPcTtqWIb9+71rkJESxIOou5isZBvyOieXw=", + "lastModified": 1764194569, + "narHash": "sha256-iUM9ktarEzThkayyZrzQ7oycPshAY2XRQqVKz0xX/L0=", "owner": "nix-community", "repo": "home-manager", - "rev": "effe4c007d6243d9e69ce2242d76a2471c1b8d5c", + "rev": "9651819d75f6c7ffaf8a9227490ac704f29659f0", "type": "github" }, "original": { @@ -636,11 +684,11 @@ ] }, "locked": { - "lastModified": 1764612577, - "narHash": "sha256-sHI+7m/ryVYf7agWkutYbvzUS07aAd8g2NVWgUqhxLg=", + "lastModified": 1764236397, + "narHash": "sha256-s/6WrJJryLI6BgphsY8l0s0UmGUg3mgkSFuvvsbN0FM=", "owner": "Jovian-Experiments", "repo": "Jovian-NixOS", - "rev": "bcb22e208cf8883004fcec3a33f2500e7dc319a5", + "rev": "50026908d1501193afdcccdf7359d1a485074eda", "type": "github" }, "original": { @@ -752,11 +800,11 @@ "nixpkgs": "nixpkgs_6" }, "locked": { - "lastModified": 1764556167, - "narHash": "sha256-/b+oEls56HDRzsSp60tsRfPFRjFebBPHq6k1I+hfPqw=", + "lastModified": 1764208886, + "narHash": "sha256-voOx8RsK3miw3EHw05nwuOS4ltzeH8tKJnVr+mxtTPQ=", "owner": "Infinidoge", "repo": "nix-minecraft", - "rev": "849d1b2b1adddfc7bddbd3be6bffd218a3f5a6fe", + "rev": "7da8a2d675f9cc56b3f6d654b4cccdca5016ac8e", "type": "github" }, "original": { @@ -852,11 +900,11 @@ ] }, "locked": { - "lastModified": 1764591717, - "narHash": "sha256-T/HMA0Bb/O6UnlGQ0Xt+wGe1j8m7eyyQ5+vVcCJslsM=", + "lastModified": 1764072830, + "narHash": "sha256-ezkjlUCohD9o9c47Ey0/I4CamSS0QEORTqGvyGqMud0=", "owner": "nix-community", "repo": "nixos-wsl", - "rev": "84d1dab290feb4865d0cfcffc7aa0cf9bc65c3b7", + "rev": "c7832dd786175e20f2697179e0e03efadffe4201", "type": "github" }, "original": { @@ -914,11 +962,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1764547213, - "narHash": "sha256-pGXM6frMKLRJmeMcQ228O1QQBuNEUjzmWx9uBd+CbXM=", + "lastModified": 1764201071, + "narHash": "sha256-ACX5IcJTSoZYBPVtgFAOHvo/FZ70n9AmaAhoeIF+O9Y=", "owner": "nixos", "repo": "nixpkgs", - "rev": "64de27c1c985895c1a9f92aaeaab4e6a4c0960f5", + "rev": "8c40e16ba896a3657226780454734265b0534f6a", "type": "github" }, "original": { @@ -946,11 +994,11 @@ }, "nixpkgs_4": { "locked": { - "lastModified": 1764618760, - "narHash": "sha256-QTUgygkdUq4sq7mXoO2Q2IPpvkKOZtTAJkbTaTjMi0A=", + "lastModified": 1764243589, + "narHash": "sha256-JoCEZJaU1Ex0MFG3A2DwTtu+jOCLigyXUAmlZLROBdg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "29a7d6eec7e1177020f62f7599e5021317219c37", + "rev": "57dcc6d4a389a7b6d1fb4cf20c9435f12b11f98d", "type": "github" }, "original": { @@ -994,11 +1042,11 @@ }, "nixpkgs_7": { "locked": { - "lastModified": 1764517877, - "narHash": "sha256-pp3uT4hHijIC8JUK5MEqeAWmParJrgBVzHLNfJDZxg4=", + "lastModified": 1763966396, + "narHash": "sha256-6eeL1YPcY1MV3DDStIDIdy/zZCDKgHdkCmsrLJFiZf0=", "owner": "nixos", "repo": "nixpkgs", - "rev": "2d293cbfa5a793b4c50d17c05ef9e385b90edf6c", + "rev": "5ae3b07d8d6527c42f17c876e404993199144b6a", "type": "github" }, "original": { @@ -1026,11 +1074,11 @@ }, "nixpkgs_9": { "locked": { - "lastModified": 1764445028, - "narHash": "sha256-ik6H/0Zl+qHYDKTXFPpzuVHSZE+uvVz2XQuQd1IVXzo=", + "lastModified": 1763618868, + "narHash": "sha256-v5afmLjn/uyD9EQuPBn7nZuaZVV9r+JerayK/4wvdWA=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "a09378c0108815dbf3961a0e085936f4146ec415", + "rev": "a8d610af3f1a5fb71e23e08434d8d61a466fc942", "type": "github" }, "original": { @@ -1139,11 +1187,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1764525349, - "narHash": "sha256-vR3vU9AwzMsBvjNeeG2inA5W/2MwseFk5NIIrLFEMHk=", + "lastModified": 1764175386, + "narHash": "sha256-LfgFqvPz3C80VjaffSjy8lLyRWfbThhB7gE7IWXHjYU=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "d646b23f000d099d845f999c2c1e05b15d9cdc78", + "rev": "71ddf07c1c75046df3bb496cf824de5c053d99ad", "type": "github" }, "original": { @@ -1204,11 +1252,11 @@ ] }, "locked": { - "lastModified": 1764483358, - "narHash": "sha256-EyyvCzXoHrbL467YSsQBTWWg4sR96MH1sPpKoSOelB4=", + "lastModified": 1764021963, + "narHash": "sha256-1m84V2ROwNEbqeS9t37/mkry23GBhfMt8qb6aHHmjuc=", "owner": "Mic92", "repo": "sops-nix", - "rev": "5aca6ff67264321d47856a2ed183729271107c9c", + "rev": "c482a1c1bbe030be6688ed7dc84f7213f304f1ec", "type": "github" }, "original": { @@ -1222,11 +1270,11 @@ "nixpkgs": "nixpkgs_9" }, "locked": { - "lastModified": 1764483358, - "narHash": "sha256-EyyvCzXoHrbL467YSsQBTWWg4sR96MH1sPpKoSOelB4=", + "lastModified": 1764021963, + "narHash": "sha256-1m84V2ROwNEbqeS9t37/mkry23GBhfMt8qb6aHHmjuc=", "owner": "Mic92", "repo": "sops-nix", - "rev": "5aca6ff67264321d47856a2ed183729271107c9c", + "rev": "c482a1c1bbe030be6688ed7dc84f7213f304f1ec", "type": "github" }, "original": { @@ -1254,11 +1302,11 @@ "tinted-zed": "tinted-zed" }, "locked": { - "lastModified": 1764550443, - "narHash": "sha256-ArO2V1YEHmEILilTj4KPtqF4gqc1q2HBrrrmygQ/UyU=", + "lastModified": 1764191810, + "narHash": "sha256-rofXPD/9TGpHveo1MTlUfpnF0MCG1/uHUB9f0rosdqc=", "owner": "nix-community", "repo": "stylix", - "rev": "794b6e1fa75177ebfeb32967f135858a1ab1ba15", + "rev": "70c444a10d0c9ef71a25580dfa79af9cd43f3a5e", "type": "github" }, "original": { @@ -1519,11 +1567,11 @@ ] }, "locked": { - "lastModified": 1764598958, - "narHash": "sha256-sJQHRL8trBoG/ArR+mUlyp5cyKU0pgQY+qDQzZGnVgM=", + "lastModified": 1764217570, + "narHash": "sha256-vgqUC6lI/gW70uekA0bpNFU6yR0tcZRfLIZcxGfN76g=", "owner": "0xc000022070", "repo": "zen-browser-flake", - "rev": "8cded25e10b13e2999241f1c73a7d4e5e5d6f69e", + "rev": "3dc281d86044322f9182b20abbc21db8824c130a", "type": "github" }, "original": { From f210c5b5adb44405826e6330eba088e1d4b7f18e Mon Sep 17 00:00:00 2001 From: chris Date: Tue, 9 Dec 2025 07:20:31 +0000 Subject: [PATCH 004/108] chore: update dependencies --- flake.lock | 130 ++++++++++++++++++++++++++--------------------------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/flake.lock b/flake.lock index adfa1cf..6f6ed7e 100644 --- a/flake.lock +++ b/flake.lock @@ -84,11 +84,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1765033957, - "narHash": "sha256-yL5IjUOne+h6AodxxqoqwPgRy2HXle6+W4Aa2GVJruk=", - "rev": "9985ce76af367e7c9e3022c5b893418059a17491", + "lastModified": 1765256668, + "narHash": "sha256-kUcoFL7wNAzJhoHACpCrBOKdjwCRKgunrCV2p6LRqeQ=", + "rev": "c57b02cdf2c8fe313072a71c3433e7110640ce97", "type": "tarball", - "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/9985ce76af367e7c9e3022c5b893418059a17491.tar.gz" + "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/c57b02cdf2c8fe313072a71c3433e7110640ce97.tar.gz" }, "original": { "type": "tarball", @@ -111,11 +111,11 @@ ] }, "locked": { - "lastModified": 1762942435, - "narHash": "sha256-zIWGs5FIytTtJN+dhDb8Yx+q4TQI/yczuL539yVcyPE=", - "rev": "0ee328404b12c65e8106bde9e9fab8abf4ecada4", + "lastModified": 1765163284, + "narHash": "sha256-tCrc6IyhXrMTTeF5lZHlwbfMBvDUr0OM5Uz+kToJ+ow=", + "rev": "986035f01ba7339c6c9d80f37aec9c5f93dfa47f", "type": "tarball", - "url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/0ee328404b12c65e8106bde9e9fab8abf4ecada4.tar.gz" + "url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/986035f01ba7339c6c9d80f37aec9c5f93dfa47f.tar.gz" }, "original": { "type": "tarball", @@ -170,11 +170,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1764915802, - "narHash": "sha256-eHTucU43sRCpvvTt5eey9htcWipS7ZN3B7ts6MiXLxo=", + "lastModified": 1765252472, + "narHash": "sha256-byMt/uMi7DJ8tRniFopDFZMO3leSjGp6GS4zWOFT+uQ=", "owner": "nix-community", "repo": "fenix", - "rev": "a83a78fd3587d9f3388f0b459ad9c2bbd6d1b6d8", + "rev": "8456b985f6652e3eef0632ee9992b439735c5544", "type": "github" }, "original": { @@ -190,11 +190,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1765024561, - "narHash": "sha256-xtfg5gNfyiyBTfWwbKgatV1sPeJjEnUczHCaSWi+crY=", + "lastModified": 1765243386, + "narHash": "sha256-JhKIDDrkGLZHFExPSzLLlmiPp2+/Sr0uzMMevzIJ4kQ=", "owner": "nix-community", "repo": "flake-firefox-nightly", - "rev": "e6f559729459a7890f01b258c33c1025800f5dbb", + "rev": "8aa54e856394834c594f423c30ae871041e263c1", "type": "github" }, "original": { @@ -574,11 +574,11 @@ "rust-overlay": "rust-overlay" }, "locked": { - "lastModified": 1764617621, - "narHash": "sha256-Eq0TvWs6xhKZs5HXH1hlrNasrHD7AOEdeLkTis//X7w=", + "lastModified": 1765227577, + "narHash": "sha256-2YyCvI3aGFkFfT6JKmaer8YyhwAk6lJwO6vCikqJwa8=", "owner": "himmelblau-idm", "repo": "himmelblau", - "rev": "c19494250d8c15e7c75e9301bdc271579a6dc77a", + "rev": "70b63803f6429dafa20be0035548072092e0e512", "type": "github" }, "original": { @@ -594,11 +594,11 @@ ] }, "locked": { - "lastModified": 1764603455, - "narHash": "sha256-Q70rxlbrxPcTtqWIb9+71rkJESxIOou5isZBvyOieXw=", + "lastModified": 1765217760, + "narHash": "sha256-BVVyAodLcAD8KOtR3yCStBHSE0WAH/xQWH9f0qsxbmk=", "owner": "nix-community", "repo": "home-manager", - "rev": "effe4c007d6243d9e69ce2242d76a2471c1b8d5c", + "rev": "e5b1f87841810fc24772bf4389f9793702000c9b", "type": "github" }, "original": { @@ -636,11 +636,11 @@ ] }, "locked": { - "lastModified": 1764612577, - "narHash": "sha256-sHI+7m/ryVYf7agWkutYbvzUS07aAd8g2NVWgUqhxLg=", + "lastModified": 1764922999, + "narHash": "sha256-LSvUxKm6S6ZAd/otQSkAHd3+8KJhi8OwGJGSe0K//B8=", "owner": "Jovian-Experiments", "repo": "Jovian-NixOS", - "rev": "bcb22e208cf8883004fcec3a33f2500e7dc319a5", + "rev": "9b9ead1b5591b68f4048e7205ba1397bc85ce6c4", "type": "github" }, "original": { @@ -655,11 +655,11 @@ "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1764506612, - "narHash": "sha256-47a2OvGsq1AfffWQqKAGlB9GjmoVa1yXVyfZP3f3kog=", + "lastModified": 1765111385, + "narHash": "sha256-Gn8IIq9FGLvQSDK2bXKzsqqkgKExTExLkYfH7n8Nnpk=", "owner": "nix-community", "repo": "lib-aggregate", - "rev": "f7208cc4a3200a2573fc566066ef4d3c041bc924", + "rev": "e562ca084a8b3490337d446f1e0d6afadb509d1e", "type": "github" }, "original": { @@ -752,11 +752,11 @@ "nixpkgs": "nixpkgs_6" }, "locked": { - "lastModified": 1764556167, - "narHash": "sha256-/b+oEls56HDRzsSp60tsRfPFRjFebBPHq6k1I+hfPqw=", + "lastModified": 1765245994, + "narHash": "sha256-6mra5F/nfee/MXqSXMSxSpjll6U/jfo8D9X+5H2ldmM=", "owner": "Infinidoge", "repo": "nix-minecraft", - "rev": "849d1b2b1adddfc7bddbd3be6bffd218a3f5a6fe", + "rev": "b83769c7fd3f3ab87221fdfda23f454ae95efc46", "type": "github" }, "original": { @@ -852,11 +852,11 @@ ] }, "locked": { - "lastModified": 1764591717, - "narHash": "sha256-T/HMA0Bb/O6UnlGQ0Xt+wGe1j8m7eyyQ5+vVcCJslsM=", + "lastModified": 1765191003, + "narHash": "sha256-d3b3eQsdgXZDW/y4fmDuNiGBjZzwFrLhwD5i3NmM1mM=", "owner": "nix-community", "repo": "nixos-wsl", - "rev": "84d1dab290feb4865d0cfcffc7aa0cf9bc65c3b7", + "rev": "a16b061ec61831755df35fae916d19b0ac5a43cc", "type": "github" }, "original": { @@ -883,11 +883,11 @@ }, "nixpkgs-lib": { "locked": { - "lastModified": 1764465291, - "narHash": "sha256-jJ/E4B9Hp7U2ZmT3E0tD1LtAfATw/xjVf8sueNyeYmc=", + "lastModified": 1765070080, + "narHash": "sha256-5D1Mcm2dQ1aPzQ0sbXluHVUHququ8A7PKJd7M3eI9+E=", "owner": "nix-community", "repo": "nixpkgs.lib", - "rev": "e9537535ae8f4a2f78dbef0aaa0cbb6af4abd047", + "rev": "e0cad9791b0c168931ae562977703b72d9360836", "type": "github" }, "original": { @@ -914,11 +914,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1764547213, - "narHash": "sha256-pGXM6frMKLRJmeMcQ228O1QQBuNEUjzmWx9uBd+CbXM=", + "lastModified": 1765183668, + "narHash": "sha256-TBA7CE44IHYfvOPBWcyLncpVrrKEiXWPdOrF8CD6W84=", "owner": "nixos", "repo": "nixpkgs", - "rev": "64de27c1c985895c1a9f92aaeaab4e6a4c0960f5", + "rev": "fc2de1563f89f0843eba27f14576d261df0e3b80", "type": "github" }, "original": { @@ -946,11 +946,11 @@ }, "nixpkgs_4": { "locked": { - "lastModified": 1764618760, - "narHash": "sha256-QTUgygkdUq4sq7mXoO2Q2IPpvkKOZtTAJkbTaTjMi0A=", + "lastModified": 1765264094, + "narHash": "sha256-BCYwzfbI353cpjFesVAcEelBrkPOhu5cQMBNPADkEj4=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "29a7d6eec7e1177020f62f7599e5021317219c37", + "rev": "e82b0773d332dc78ba550aa46227f21057cbaff8", "type": "github" }, "original": { @@ -994,11 +994,11 @@ }, "nixpkgs_7": { "locked": { - "lastModified": 1764517877, - "narHash": "sha256-pp3uT4hHijIC8JUK5MEqeAWmParJrgBVzHLNfJDZxg4=", + "lastModified": 1764950072, + "narHash": "sha256-BmPWzogsG2GsXZtlT+MTcAWeDK5hkbGRZTeZNW42fwA=", "owner": "nixos", "repo": "nixpkgs", - "rev": "2d293cbfa5a793b4c50d17c05ef9e385b90edf6c", + "rev": "f61125a668a320878494449750330ca58b78c557", "type": "github" }, "original": { @@ -1026,11 +1026,11 @@ }, "nixpkgs_9": { "locked": { - "lastModified": 1764445028, - "narHash": "sha256-ik6H/0Zl+qHYDKTXFPpzuVHSZE+uvVz2XQuQd1IVXzo=", + "lastModified": 1764947035, + "narHash": "sha256-EYHSjVM4Ox4lvCXUMiKKs2vETUSL5mx+J2FfutM7T9w=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "a09378c0108815dbf3961a0e085936f4146ec415", + "rev": "a672be65651c80d3f592a89b3945466584a22069", "type": "github" }, "original": { @@ -1074,11 +1074,11 @@ "systems": "systems_5" }, "locked": { - "lastModified": 1764904740, - "narHash": "sha256-TzqXUQlESmS5XGJ3tR1/xdoU0vySyp6YUUpmGF5F0kY=", + "lastModified": 1765119282, + "narHash": "sha256-iI0fuBBYJMnOprGD2L+rum2P8lHMcZ5n35hzdlpwayI=", "owner": "notashelf", "repo": "nvf", - "rev": "249cabe0c5392c384c82fa9d28d3f49fbeb04266", + "rev": "26c4a7e3c33e739d474ddaf52aa4c5f3d11922ba", "type": "github" }, "original": { @@ -1139,11 +1139,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1764525349, - "narHash": "sha256-vR3vU9AwzMsBvjNeeG2inA5W/2MwseFk5NIIrLFEMHk=", + "lastModified": 1765120009, + "narHash": "sha256-nG76b87rkaDzibWbnB5bYDm6a52b78A+fpm+03pqYIw=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "d646b23f000d099d845f999c2c1e05b15d9cdc78", + "rev": "5e3e9c4e61bba8a5e72134b9ffefbef8f531d008", "type": "github" }, "original": { @@ -1204,11 +1204,11 @@ ] }, "locked": { - "lastModified": 1764483358, - "narHash": "sha256-EyyvCzXoHrbL467YSsQBTWWg4sR96MH1sPpKoSOelB4=", + "lastModified": 1765231718, + "narHash": "sha256-qdBzo6puTgG4G2RHG0PkADg22ZnQo1JmSVFRxrD4QM4=", "owner": "Mic92", "repo": "sops-nix", - "rev": "5aca6ff67264321d47856a2ed183729271107c9c", + "rev": "7fd1416aba1865eddcdec5bb11339b7222c2363e", "type": "github" }, "original": { @@ -1222,11 +1222,11 @@ "nixpkgs": "nixpkgs_9" }, "locked": { - "lastModified": 1764483358, - "narHash": "sha256-EyyvCzXoHrbL467YSsQBTWWg4sR96MH1sPpKoSOelB4=", + "lastModified": 1765231718, + "narHash": "sha256-qdBzo6puTgG4G2RHG0PkADg22ZnQo1JmSVFRxrD4QM4=", "owner": "Mic92", "repo": "sops-nix", - "rev": "5aca6ff67264321d47856a2ed183729271107c9c", + "rev": "7fd1416aba1865eddcdec5bb11339b7222c2363e", "type": "github" }, "original": { @@ -1254,11 +1254,11 @@ "tinted-zed": "tinted-zed" }, "locked": { - "lastModified": 1764550443, - "narHash": "sha256-ArO2V1YEHmEILilTj4KPtqF4gqc1q2HBrrrmygQ/UyU=", + "lastModified": 1765047449, + "narHash": "sha256-VQcqjJ2g0kT9TW4ENwA2HBQJzfbCUd5s1Wm3K+R2QZY=", "owner": "nix-community", "repo": "stylix", - "rev": "794b6e1fa75177ebfeb32967f135858a1ab1ba15", + "rev": "bd00e01aab676aee88e6cc5c9238b4a5a7d6639a", "type": "github" }, "original": { @@ -1519,11 +1519,11 @@ ] }, "locked": { - "lastModified": 1764598958, - "narHash": "sha256-sJQHRL8trBoG/ArR+mUlyp5cyKU0pgQY+qDQzZGnVgM=", + "lastModified": 1765175766, + "narHash": "sha256-M4zs4bVUv0UNuVGspwwlcGs5FpCDt52LQBA5a9nj5Lg=", "owner": "0xc000022070", "repo": "zen-browser-flake", - "rev": "8cded25e10b13e2999241f1c73a7d4e5e5d6f69e", + "rev": "5126a8426773dc213a8c0f0d646aca116194dab6", "type": "github" }, "original": { From 03e8fea254eb6e3f6d0c51fdcf3dd12abab55bf3 Mon Sep 17 00:00:00 2001 From: chris Date: Tue, 9 Dec 2025 14:53:08 +0000 Subject: [PATCH 005/108] chore(secrets): set secret "grafana/oidc_id" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 086d86d..4e02424 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -27,6 +27,8 @@ mydia: qbittorrent: password_hash: ENC[AES256_GCM,data:QWuQYmfBn9eLDYztH7TmQvw74MvmzCQ98OlBtyjm1Icr2c63epRuHWzQbm+Q+1jrCSiQreOB3ZyjLzkeV6SlLonryUSD71uBWVwctgPXO0XDrxE1Vi6dkiwC3TF65JTMDhyjDLEj1YkiMP25Fz5NidJTP/r9GlXTfM7gjWo=,iv:bpgL5IoAv+1PUtgNIjLcbzN8C9z55ndypz4LEELAhLc=,tag:VB+XTCwLeIEYKnOr/0f7zA==,type:str] password: ENC[AES256_GCM,data:UepYY6UjJV/jo2aXTOEnKRtsjSqOSYPQlKlrAa7rf9rdnt2UXGjCkvN+A72pICuIBCAmhXZBAUMvmWTV9trk6NREHe0cY1xTC7pNv3x9TM/ZQmH498pbT/95pYAKwouHp9heJQ==,iv:FzjF+xPoaOp+gplxpz940V2dkWSTWe8dWUxexCoxxHc=,tag:TDZsboq9fEmmBrwJN/HTpQ==,type:str] +grafana: + oidc_id: ENC[AES256_GCM,data:NVdIgCQ6nz4BSUDJYCKyILtK,iv:tcljy9PzC/yyd7TSdngyJt+uh60uXi2PKu47czErbaQ=,tag:zE4q3dD4UQaHIpGeZ1L48Q==,type:str] sops: age: - recipient: age19qfpf980tadguqq44zf6xwvjvl428dyrj46ha3n6aeqddwhtnuqqml7etq @@ -47,7 +49,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-12-04T11:24:52Z" - mac: ENC[AES256_GCM,data:jIgkl1lcVDSlKqJs9fjaHUAZsGL+22T86/qqKyDziHl0+VU763Ezwm8P+la+55jIIT2zLhFcUjhn2BabBi90OeEPztAC4rGpZj6+ZZ0GDCj/JhjPAAo3LgAKOCG0Xgf8MZWr/rXd6bLhW7Qj36PMJnap26rjEiUZeSvpWS2dz8g=,iv:CDx8fBI9Dl1uwrbMD1fa7/h3C7haK3xZxJI59mtL1LA=,tag:2UDRFJoevGEBKZA/9eUiOw==,type:str] + lastmodified: "2025-12-09T14:53:07Z" + mac: ENC[AES256_GCM,data:/dncb2tqTpQiUdAtmR9xhd22Sl2RBUtL7OIawP25ZHd1S6fwAAiwSC/8p3Zn/dYXv4M4Gq/EJ6CzrZD2V5hYob/K1DlEmBZDf1O53oDU+CneMo0SGXwWI9aZWJRwHW2r+zi6wO2cfQKStryPTJe2gwZFzokSG7+zC2x18yKKdhw=,iv:YVZfXN1iUcnxs94f+ikL8bVVAIM4a2Yh9gU71LhVJ8c=,tag:1nCTSVFhpevhCImLayWffg==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From f295f0fc487acc96e1ae6fde0d42a5f60549dede Mon Sep 17 00:00:00 2001 From: chris Date: Tue, 9 Dec 2025 14:53:26 +0000 Subject: [PATCH 006/108] chore(secrets): set secret "grafana/oidc_secret" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 4e02424..745479d 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -29,6 +29,7 @@ qbittorrent: password: ENC[AES256_GCM,data:UepYY6UjJV/jo2aXTOEnKRtsjSqOSYPQlKlrAa7rf9rdnt2UXGjCkvN+A72pICuIBCAmhXZBAUMvmWTV9trk6NREHe0cY1xTC7pNv3x9TM/ZQmH498pbT/95pYAKwouHp9heJQ==,iv:FzjF+xPoaOp+gplxpz940V2dkWSTWe8dWUxexCoxxHc=,tag:TDZsboq9fEmmBrwJN/HTpQ==,type:str] grafana: oidc_id: ENC[AES256_GCM,data:NVdIgCQ6nz4BSUDJYCKyILtK,iv:tcljy9PzC/yyd7TSdngyJt+uh60uXi2PKu47czErbaQ=,tag:zE4q3dD4UQaHIpGeZ1L48Q==,type:str] + oidc_secret: ENC[AES256_GCM,data:b7qILK9ZHW2khtM1Hl/KdjCv3Wq6eOo2Ym/cbjcMB8/3Hn2UelpP4K4lFyiV3bn1/GF6Jl5Z7A0EwMybOx0InA==,iv:3HL/7BiyObwT8DmFxzNPI9CdmCH/4j/4oc9x7qBE1k0=,tag:dBhcq1zLKy6N+jp/v42R4A==,type:str] sops: age: - recipient: age19qfpf980tadguqq44zf6xwvjvl428dyrj46ha3n6aeqddwhtnuqqml7etq @@ -49,7 +50,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-12-09T14:53:07Z" - mac: ENC[AES256_GCM,data:/dncb2tqTpQiUdAtmR9xhd22Sl2RBUtL7OIawP25ZHd1S6fwAAiwSC/8p3Zn/dYXv4M4Gq/EJ6CzrZD2V5hYob/K1DlEmBZDf1O53oDU+CneMo0SGXwWI9aZWJRwHW2r+zi6wO2cfQKStryPTJe2gwZFzokSG7+zC2x18yKKdhw=,iv:YVZfXN1iUcnxs94f+ikL8bVVAIM4a2Yh9gU71LhVJ8c=,tag:1nCTSVFhpevhCImLayWffg==,type:str] + lastmodified: "2025-12-09T14:53:25Z" + mac: ENC[AES256_GCM,data:bb6YXIClIRCEyvQEYQpuzjqSgAvcHr0Avb0t+HSIoIY69cnCojNxb1cN53b0HBV69qOiXgKlXcQrI4ry2qokfRbAAlp9w5g978+E3fnlefBxGY2wHEeJZL/27BXq7nEfvdepcLVM+o5PMn0iiYUR42OYJkXxAHXqhYNdt9kWjMM=,iv:QfIB9WckrxK2YXMTNVWgUjt6F+QG96KzUlwlYPM5WBc=,tag:X69yLpEsu//3HgtSuHoQig==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From 6af9101a135e9c38215177043468eaea0e38b719 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Tue, 9 Dec 2025 16:17:26 +0100 Subject: [PATCH 007/108] feat: add oidc from sops for grafana --- .../observability/grafana/default.nix | 33 ++++++++++++++----- systems/x86_64-linux/ulmo/default.nix | 6 ++++ 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/modules/nixos/services/observability/grafana/default.nix b/modules/nixos/services/observability/grafana/default.nix index 6503493..05d3570 100644 --- a/modules/nixos/services/observability/grafana/default.nix +++ b/modules/nixos/services/observability/grafana/default.nix @@ -1,5 +1,10 @@ -{ pkgs, config, lib, namespace, ... }: -let +{ + pkgs, + config, + lib, + namespace, + ... +}: let inherit (lib.modules) mkIf; inherit (lib.options) mkEnableOption; @@ -7,8 +12,7 @@ let db_user = "grafana"; db_name = "grafana"; -in -{ +in { options.${namespace}.services.observability.grafana = { enable = mkEnableOption "enable Grafana"; }; @@ -35,8 +39,8 @@ in "auth.generic_oauth" = { enable = true; name = "Zitadel"; - client_id = "334170712283611395"; - client_secret = "AFjypmURdladmQn1gz2Ke0Ta5LQXapnuKkALVZ43riCL4qWicgV2Z6RlwpoWBZg1"; + client_id = "$__file{${config.sops.secrets."grafana/oidc_id".path}}"; + client_secret = "$__file{${config.sops.secrets."grafana/oidc_secret".path}}"; scopes = "openid email profile offline_access urn:zitadel:iam:org:project:roles"; email_attribute_path = "email"; login_attribute_path = "username"; @@ -64,7 +68,7 @@ in allow_sign_up = false; allow_org_create = false; viewers_can_edit = false; - + default_theme = "system"; }; @@ -115,7 +119,7 @@ in postgresql = { enable = true; - ensureDatabases = [ db_name ]; + ensureDatabases = [db_name]; ensureUsers = [ { name = db_user; @@ -126,5 +130,18 @@ in }; environment.etc."/grafana/dashboards/default.json".source = ./dashboards/default.json; + + sops = { + secrets = { + "grafana/oidc_id" = { + owner = "grafana"; + group = "grafana"; + }; + "grafana/oidc_secret" = { + owner = "grafana"; + group = "grafana"; + }; + }; + }; }; } diff --git a/systems/x86_64-linux/ulmo/default.nix b/systems/x86_64-linux/ulmo/default.nix index 9d12de8..e661dd8 100644 --- a/systems/x86_64-linux/ulmo/default.nix +++ b/systems/x86_64-linux/ulmo/default.nix @@ -118,6 +118,12 @@ grantTypes = ["authorizationCode"]; responseTypes = ["code"]; }; + + grafana = { + redirectUris = ["http://localhost:9001/login/generic_oauth"]; + grantTypes = ["authorizationCode"]; + responseTypes = ["code"]; + }; }; }; }; From 4624b0b0f7037da54c00b7d1bdf68653c0ee8ea8 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Tue, 9 Dec 2025 16:18:09 +0100 Subject: [PATCH 008/108] wip: setting up download clients in the arr stack --- modules/nixos/services/media/default.nix | 20 ------- .../nixos/services/media/servarr/default.nix | 54 ++++++++++++++++--- 2 files changed, 47 insertions(+), 27 deletions(-) diff --git a/modules/nixos/services/media/default.nix b/modules/nixos/services/media/default.nix index d257aea..79d2307 100644 --- a/modules/nixos/services/media/default.nix +++ b/modules/nixos/services/media/default.nix @@ -106,25 +106,5 @@ in { }; systemd.services.jellyfin.serviceConfig.killSignal = lib.mkForce "SIGKILL"; - - sops = { - secrets = { - # "qbittorrent/password" = {}; - "qbittorrent/password_hash" = {}; - }; - - templates = { - "qbittorrent/password.conf" = { - owner = cfg.user; - group = cfg.group; - restartUnits = ["qbittorrent.service"]; - path = "${config.services.qbittorrent.profileDir}/qBittorrent/config/password.conf"; - content = '' - [Preferences] - WebUI\Password_PBKDF2="${config.sops.placeholder."qbittorrent/password_hash"}" - ''; - }; - }; - }; }; } diff --git a/modules/nixos/services/media/servarr/default.nix b/modules/nixos/services/media/servarr/default.nix index 373e09b..c09e66f 100644 --- a/modules/nixos/services/media/servarr/default.nix +++ b/modules/nixos/services/media/servarr/default.nix @@ -72,10 +72,8 @@ in { group = "media"; }); })) - |> lib.mkMerge - |> (set: - set - // { + |> lib.concat [ + { qbittorrent = { enable = true; openFirewall = true; @@ -86,6 +84,7 @@ in { Prefecences.WebUI = { Username = "admin"; + Password_PBKDF2 = "@ByteArray(JpfX3wSUcMolUFD+8AD67w==:fr5kmc6sK9xsCfGW6HkPX2K1lPYHL6g2ncLLwuOVmjphmxkwBJ8pi/XQDsDWzyM/MRh5zPhUld2Xqn8o7BWv3Q==)"; }; }; @@ -97,7 +96,7 @@ in { sabnzbd = { enable = true; openFirewall = true; - configFile = "${cfg.path}/sabnzbd/config.ini"; + configFile = config.sops.templates."sabnzbd/config.ini".path; user = "sabnzbd"; group = "media"; @@ -113,7 +112,9 @@ in { ensureDBOwnership = true; }); }; - }); + } + ] + |> lib.mkMerge; systemd.services = cfg @@ -125,6 +126,8 @@ in { ... }: (mkIf enable { "${service}ApplyTerraform" = let + config' = config; + terraformConfiguration = inputs.terranix.lib.terranixConfiguration { inherit system; @@ -168,6 +171,30 @@ in { |> lib.imap (i: f: lib.nameValuePair "local${toString i}" {path = f;}) |> lib.listToAttrs ); + + "${service}_download_client_qbittorrent" = mkIf (lib.elem service ["radarr" "sonarr" "lidarr" "whisparr"]) { + "main" = { + name = "qBittorrent"; + enable = true; + priority = 1; + host = "localhost"; + username = "admin"; + password = "poChieN5feeph0igeaCadeJ9Xux0ohmuy6ruH5ieThaPheib3iuzoo0ahw1aiceif1feegioh9Aimau0pai5thoh5ieH0aechohw"; + url_base = "/"; + port = 2008; + }; + }; + + # "${service}_download_client_sabnzbd" = mkIf (lib.elem service ["radarr" "sonarr" "lidarr" "whisparr"]) { + # "main" = { + # name = "SABnzbd"; + # enable = true; + # priority = 1; + # host = "localhost"; + # url_base = "/"; + # port = 8080; + # }; + # }; }; }; }) @@ -204,7 +231,7 @@ in { cp -f ${terraformConfiguration} config.tf.json # Initialize OpenTofu - ${lib.getExe pkgs.opentofu} init -upgrade + ${lib.getExe pkgs.opentofu} init # Run the infrastructure code ${lib.getExe pkgs.opentofu} \ @@ -272,6 +299,19 @@ in { }; }; })) + |> lib.concat [ + { + templates = { + "sabnzbd/config.ini" = { + owner = "sabnzbd"; + group = "media"; + content = '' + + ''; + }; + }; + } + ] |> lib.mkMerge; }; } From f2a0d05f16f5d44b5806b6eebb119122bfa95239 Mon Sep 17 00:00:00 2001 From: chris Date: Wed, 10 Dec 2025 07:21:08 +0000 Subject: [PATCH 009/108] chore(secrets): set secret "qbittorrent/password_hash" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 745479d..12a3115 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -25,7 +25,7 @@ mydia: secret_key_base: ENC[AES256_GCM,data:yG7HJ5r74Qtxbeyf8F6dA0uHv2pQ8YAJKlKiKjS+m24JRvJWQaTThJ+c5HbuUa6R3e9XtVHchhlVPkF0Is/b+g==,iv:v65xdRr4JdKZmBtjZ08/J3LLqnphSGt9QfVPNQ2x/xg=,tag:n7tD2dhr4IJn1LWM9WW8UA==,type:str] guardian_secret: ENC[AES256_GCM,data:OjnNFSHlecL+qXwlhTm++itRM6ga5E5KrSJxbgIUpbMEkIWgu3xhRtnPdipXbedgall0XdO/s+jnWCagZX94BA==,iv:DukdKvm9vey8BWUiml20tgA/Vji1XVX4+sUPge9nTk0=,tag:q3HdvgUYqR0APiaFz0ul5Q==,type:str] qbittorrent: - password_hash: ENC[AES256_GCM,data:QWuQYmfBn9eLDYztH7TmQvw74MvmzCQ98OlBtyjm1Icr2c63epRuHWzQbm+Q+1jrCSiQreOB3ZyjLzkeV6SlLonryUSD71uBWVwctgPXO0XDrxE1Vi6dkiwC3TF65JTMDhyjDLEj1YkiMP25Fz5NidJTP/r9GlXTfM7gjWo=,iv:bpgL5IoAv+1PUtgNIjLcbzN8C9z55ndypz4LEELAhLc=,tag:VB+XTCwLeIEYKnOr/0f7zA==,type:str] + password_hash: ENC[AES256_GCM,data:yCfCslj01wtfwzzPOGlwA6wLLf+EUuEweYa3ZxvDtd/VGMxuV38quV+ob1Of+W0UH3+U4Qmgh4BK3I3IJZuKOvNdkZ0i81YBwW6cgvZUmnxwh8wokpNzxCKbYk5nF7y7SaGEdzQLvV7ad3fNMJsQ+s2zCsKWbm+j8Bwgq0E=,iv:IIktPS9pYXaYPzH0r4wrkp31CpunKnr70Ainu6hOeWY=,tag:bYCfhDfIwiQZ1tKAvITewQ==,type:str] password: ENC[AES256_GCM,data:UepYY6UjJV/jo2aXTOEnKRtsjSqOSYPQlKlrAa7rf9rdnt2UXGjCkvN+A72pICuIBCAmhXZBAUMvmWTV9trk6NREHe0cY1xTC7pNv3x9TM/ZQmH498pbT/95pYAKwouHp9heJQ==,iv:FzjF+xPoaOp+gplxpz940V2dkWSTWe8dWUxexCoxxHc=,tag:TDZsboq9fEmmBrwJN/HTpQ==,type:str] grafana: oidc_id: ENC[AES256_GCM,data:NVdIgCQ6nz4BSUDJYCKyILtK,iv:tcljy9PzC/yyd7TSdngyJt+uh60uXi2PKu47czErbaQ=,tag:zE4q3dD4UQaHIpGeZ1L48Q==,type:str] @@ -50,7 +50,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-12-09T14:53:25Z" - mac: ENC[AES256_GCM,data:bb6YXIClIRCEyvQEYQpuzjqSgAvcHr0Avb0t+HSIoIY69cnCojNxb1cN53b0HBV69qOiXgKlXcQrI4ry2qokfRbAAlp9w5g978+E3fnlefBxGY2wHEeJZL/27BXq7nEfvdepcLVM+o5PMn0iiYUR42OYJkXxAHXqhYNdt9kWjMM=,iv:QfIB9WckrxK2YXMTNVWgUjt6F+QG96KzUlwlYPM5WBc=,tag:X69yLpEsu//3HgtSuHoQig==,type:str] + lastmodified: "2025-12-10T07:21:06Z" + mac: ENC[AES256_GCM,data:eKWrwVqOXeVz0+IRushA+N+qN6OL9gTXArNELBjIovMrxYwEgDDa+cqIQ4rpkFBzkMZE+tBhM2K+LFOI9CVrEb4LfhvGx75QI9yz2n7etJJlrXD06yKmI1dbkQ1D0zcpGkuf7poa+R06B+PPgDjI+NkgCZYaeZ4VvOlAEubVAR0=,iv:OQYowrnu3saxSA1R9xVcD1BCC936KRLC7HIQ6m0+uS0=,tag:SnBjtM1hrrN860vO8oP/3w==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From d09699f6e9d9a1941a69d9dee0aeb715d96bfc62 Mon Sep 17 00:00:00 2001 From: chris Date: Wed, 10 Dec 2025 09:29:33 +0000 Subject: [PATCH 010/108] chore(secrets): set secret "sabnzbd/sunnyweb/password" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 12a3115..606c924 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -30,6 +30,9 @@ qbittorrent: grafana: oidc_id: ENC[AES256_GCM,data:NVdIgCQ6nz4BSUDJYCKyILtK,iv:tcljy9PzC/yyd7TSdngyJt+uh60uXi2PKu47czErbaQ=,tag:zE4q3dD4UQaHIpGeZ1L48Q==,type:str] oidc_secret: ENC[AES256_GCM,data:b7qILK9ZHW2khtM1Hl/KdjCv3Wq6eOo2Ym/cbjcMB8/3Hn2UelpP4K4lFyiV3bn1/GF6Jl5Z7A0EwMybOx0InA==,iv:3HL/7BiyObwT8DmFxzNPI9CdmCH/4j/4oc9x7qBE1k0=,tag:dBhcq1zLKy6N+jp/v42R4A==,type:str] +sabnzbd: + sunnyweb: + password: ENC[AES256_GCM,data:flw8AahqO1Mx,iv:Qhu8iVWMzzqy18y8dj3aHoBnSZatm74/tYvZ456l2sA=,tag:sCYBdw7kD0zJZFFr5EyPIQ==,type:str] sops: age: - recipient: age19qfpf980tadguqq44zf6xwvjvl428dyrj46ha3n6aeqddwhtnuqqml7etq @@ -50,7 +53,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-12-10T07:21:06Z" - mac: ENC[AES256_GCM,data:eKWrwVqOXeVz0+IRushA+N+qN6OL9gTXArNELBjIovMrxYwEgDDa+cqIQ4rpkFBzkMZE+tBhM2K+LFOI9CVrEb4LfhvGx75QI9yz2n7etJJlrXD06yKmI1dbkQ1D0zcpGkuf7poa+R06B+PPgDjI+NkgCZYaeZ4VvOlAEubVAR0=,iv:OQYowrnu3saxSA1R9xVcD1BCC936KRLC7HIQ6m0+uS0=,tag:SnBjtM1hrrN860vO8oP/3w==,type:str] + lastmodified: "2025-12-10T09:29:32Z" + mac: ENC[AES256_GCM,data:8gr/VN9Hx5YIWKmqSpRTknlVZz9oJOCN8D43jq+gS9hRaLzbZcnKaxeECPfKwcSaKa3dyGNcyegQqsW8/7aC0dU5kQRmPrI5DftWRhjJRBgFyng8sMln8u8FcfgX1PkmPN/vTvWCzKWgaCiIt9f6nmTlGX7GAbatMax/tU/04Tw=,iv:js7VPZrvhll1fkh+IVDktqS+FbxfYO2g0gEQ04b5jc0=,tag:fNWrytKXUcuGApOoA9hUsg==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From a2b1664c2272f30f2b5cde8175ba94a7cf23b12d Mon Sep 17 00:00:00 2001 From: chris Date: Wed, 10 Dec 2025 09:30:20 +0000 Subject: [PATCH 011/108] chore(secrets): set secret "sabnzbd/sunnyweb/username" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 606c924..cbb8b16 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -33,6 +33,7 @@ grafana: sabnzbd: sunnyweb: password: ENC[AES256_GCM,data:flw8AahqO1Mx,iv:Qhu8iVWMzzqy18y8dj3aHoBnSZatm74/tYvZ456l2sA=,tag:sCYBdw7kD0zJZFFr5EyPIQ==,type:str] + username: ENC[AES256_GCM,data:IboJ8WDWuVNgvrk7c3V8I5S6Xg==,iv:BRohMuQFQz2S+HFasIaok6npT3C5v/SlhAhbLQXfB0s=,tag:M3/u0WBQ3AufHqe4DCtsrA==,type:str] sops: age: - recipient: age19qfpf980tadguqq44zf6xwvjvl428dyrj46ha3n6aeqddwhtnuqqml7etq @@ -53,7 +54,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-12-10T09:29:32Z" - mac: ENC[AES256_GCM,data:8gr/VN9Hx5YIWKmqSpRTknlVZz9oJOCN8D43jq+gS9hRaLzbZcnKaxeECPfKwcSaKa3dyGNcyegQqsW8/7aC0dU5kQRmPrI5DftWRhjJRBgFyng8sMln8u8FcfgX1PkmPN/vTvWCzKWgaCiIt9f6nmTlGX7GAbatMax/tU/04Tw=,iv:js7VPZrvhll1fkh+IVDktqS+FbxfYO2g0gEQ04b5jc0=,tag:fNWrytKXUcuGApOoA9hUsg==,type:str] + lastmodified: "2025-12-10T09:30:19Z" + mac: ENC[AES256_GCM,data:AyVTsx8XHSD5HVShx5C4qivTvWVftrWmcr68BrkWwzaXZ+UCKIdNKITO9ByQwDqP6ZrU+lFoZRUSzJ/xeHdfgbvGuqGnDPqWPVKH030jmu7Y19mpBrsRSwrnEtu8959uhazo8+NMwFo3MoQFEeGWsr7RsV2bKSqVJxDcF0H0nKg=,iv:prrZlz+jaIP4GlBbGygpSlBMof2eMSvcsZQQAcXdhyI=,tag:WR5hJmj2zuiVUwl8Jec0Aw==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From 01410856f628bd1e5b14b8d3d92a72919e0790db Mon Sep 17 00:00:00 2001 From: chris Date: Wed, 10 Dec 2025 12:55:32 +0000 Subject: [PATCH 012/108] chore(secrets): set secret "sabnzbd/apikey" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index cbb8b16..3f34c21 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -34,6 +34,7 @@ sabnzbd: sunnyweb: password: ENC[AES256_GCM,data:flw8AahqO1Mx,iv:Qhu8iVWMzzqy18y8dj3aHoBnSZatm74/tYvZ456l2sA=,tag:sCYBdw7kD0zJZFFr5EyPIQ==,type:str] username: ENC[AES256_GCM,data:IboJ8WDWuVNgvrk7c3V8I5S6Xg==,iv:BRohMuQFQz2S+HFasIaok6npT3C5v/SlhAhbLQXfB0s=,tag:M3/u0WBQ3AufHqe4DCtsrA==,type:str] + apikey: ENC[AES256_GCM,data:j5sPXKbBhMdNHOuoTfZ+c8nGu5JameOgK2z428iLdP01Hi6MvHVaN8Zs8YxMoSBtOjdtIEC8MS+3m1S1rU/P4pCRfZpK5ua1DBHq4l0xROUqokFWjDcAmJJv3pYXl0cQxQcGKQ==,iv:v5hu3gmO1Zn1FfXkHLPGN9f7JOcQjzoQahdqJwfM+xY=,tag:uI1LFcTgcyRgAaTJ1kzKow==,type:str] sops: age: - recipient: age19qfpf980tadguqq44zf6xwvjvl428dyrj46ha3n6aeqddwhtnuqqml7etq @@ -54,7 +55,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-12-10T09:30:19Z" - mac: ENC[AES256_GCM,data:AyVTsx8XHSD5HVShx5C4qivTvWVftrWmcr68BrkWwzaXZ+UCKIdNKITO9ByQwDqP6ZrU+lFoZRUSzJ/xeHdfgbvGuqGnDPqWPVKH030jmu7Y19mpBrsRSwrnEtu8959uhazo8+NMwFo3MoQFEeGWsr7RsV2bKSqVJxDcF0H0nKg=,iv:prrZlz+jaIP4GlBbGygpSlBMof2eMSvcsZQQAcXdhyI=,tag:WR5hJmj2zuiVUwl8Jec0Aw==,type:str] + lastmodified: "2025-12-10T12:55:31Z" + mac: ENC[AES256_GCM,data:qUZOcbHCssZC5Td1g9+TZFMccgHSDivTPF71+uGpyI88AuZAMt07kZuIuWcP4V8m633fl6WtmDAN/UP9IjbgBSUNLHpRcR5cOCAlxtLu0R0HNNIwMdxgseFTqPV/h/dMNSlEbAu06VZlt9S2CSkuTeyrP+GTpcskPJWF/RC50dk=,iv:QdwALTd3/1eKN7V3YtMf3Av0+XP6D59sQzDwqn7maOU=,tag:MX64z96lIn6RZBBsATi1ZQ==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From 7751f756b7b8edf2ff2e60cbc1b4feed3ae59a33 Mon Sep 17 00:00:00 2001 From: chris Date: Wed, 10 Dec 2025 15:41:34 +0000 Subject: [PATCH 013/108] chore: update dependencies --- flake.lock | 80 +++++++++++++++++++++++++++--------------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/flake.lock b/flake.lock index 6f6ed7e..cd4b600 100644 --- a/flake.lock +++ b/flake.lock @@ -84,11 +84,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1765256668, - "narHash": "sha256-kUcoFL7wNAzJhoHACpCrBOKdjwCRKgunrCV2p6LRqeQ=", - "rev": "c57b02cdf2c8fe313072a71c3433e7110640ce97", + "lastModified": 1765346666, + "narHash": "sha256-UR8bVZF12rA7yI3jdqvlTA50NUXf3F8H6GZvLYiDqYM=", + "rev": "7c9a2e4fb9d90f213f3bf3782ee460e669231bca", "type": "tarball", - "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/c57b02cdf2c8fe313072a71c3433e7110640ce97.tar.gz" + "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/7c9a2e4fb9d90f213f3bf3782ee460e669231bca.tar.gz" }, "original": { "type": "tarball", @@ -130,11 +130,11 @@ ] }, "locked": { - "lastModified": 1764627417, - "narHash": "sha256-D6xc3Rl8Ab6wucJWdvjNsGYGSxNjQHzRc2EZ6eeQ6l4=", + "lastModified": 1765326679, + "narHash": "sha256-fTLX9kDwLr9Y0rH/nG+h1XG5UU+jBcy0PFYn5eneRX8=", "owner": "nix-community", "repo": "disko", - "rev": "5a88a6eceb8fd732b983e72b732f6f4b8269bef3", + "rev": "d64e5cdca35b5fad7c504f615357a7afe6d9c49e", "type": "github" }, "original": { @@ -190,11 +190,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1765243386, - "narHash": "sha256-JhKIDDrkGLZHFExPSzLLlmiPp2+/Sr0uzMMevzIJ4kQ=", + "lastModified": 1765370621, + "narHash": "sha256-3gAVH9nYc2E82tIXKFv2lMe4JohglxJtPgs0ZmXkx9c=", "owner": "nix-community", "repo": "flake-firefox-nightly", - "rev": "8aa54e856394834c594f423c30ae871041e263c1", + "rev": "ea98c8041dad75efc80ec036643a32b12467c8b7", "type": "github" }, "original": { @@ -594,11 +594,11 @@ ] }, "locked": { - "lastModified": 1765217760, - "narHash": "sha256-BVVyAodLcAD8KOtR3yCStBHSE0WAH/xQWH9f0qsxbmk=", + "lastModified": 1765337252, + "narHash": "sha256-HuWQp8fM25fyWflbuunQkQI62Hg0ecJxWD52FAgmxqY=", "owner": "nix-community", "repo": "home-manager", - "rev": "e5b1f87841810fc24772bf4389f9793702000c9b", + "rev": "13cc1efd78b943b98c08d74c9060a5b59bf86921", "type": "github" }, "original": { @@ -636,11 +636,11 @@ ] }, "locked": { - "lastModified": 1764922999, - "narHash": "sha256-LSvUxKm6S6ZAd/otQSkAHd3+8KJhi8OwGJGSe0K//B8=", + "lastModified": 1765365489, + "narHash": "sha256-L0uvs+o8P5JzEcTPe2WPA48+0ZiO6+8nlfh7XSjQql4=", "owner": "Jovian-Experiments", "repo": "Jovian-NixOS", - "rev": "9b9ead1b5591b68f4048e7205ba1397bc85ce6c4", + "rev": "ddf5db234397043a8af5c38433b5ae933d660f27", "type": "github" }, "original": { @@ -752,11 +752,11 @@ "nixpkgs": "nixpkgs_6" }, "locked": { - "lastModified": 1765245994, - "narHash": "sha256-6mra5F/nfee/MXqSXMSxSpjll6U/jfo8D9X+5H2ldmM=", + "lastModified": 1765332486, + "narHash": "sha256-nVTejyI8w3ePrX4tW3lBLLg3DheqhRuxtiRefT+ynrk=", "owner": "Infinidoge", "repo": "nix-minecraft", - "rev": "b83769c7fd3f3ab87221fdfda23f454ae95efc46", + "rev": "a3bdc14045dc7e5fb7a94ab11064766f472279eb", "type": "github" }, "original": { @@ -852,11 +852,11 @@ ] }, "locked": { - "lastModified": 1765191003, - "narHash": "sha256-d3b3eQsdgXZDW/y4fmDuNiGBjZzwFrLhwD5i3NmM1mM=", + "lastModified": 1765376994, + "narHash": "sha256-dsgdFdj8+qh81XPB/9SlwvuhJMHPjqsf7Zk0AnsdVpY=", "owner": "nix-community", "repo": "nixos-wsl", - "rev": "a16b061ec61831755df35fae916d19b0ac5a43cc", + "rev": "30f6a14293df4938c35173a73efdeba450653d0a", "type": "github" }, "original": { @@ -914,11 +914,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1765183668, - "narHash": "sha256-TBA7CE44IHYfvOPBWcyLncpVrrKEiXWPdOrF8CD6W84=", + "lastModified": 1765357816, + "narHash": "sha256-Uh7y3tL9SUzMjM8eO9CMqf30pPpa1i+P3asBijc32lY=", "owner": "nixos", "repo": "nixpkgs", - "rev": "fc2de1563f89f0843eba27f14576d261df0e3b80", + "rev": "004943ed3cf9de5805a0da377599d1bfdd47a98a", "type": "github" }, "original": { @@ -946,11 +946,11 @@ }, "nixpkgs_4": { "locked": { - "lastModified": 1765264094, - "narHash": "sha256-BCYwzfbI353cpjFesVAcEelBrkPOhu5cQMBNPADkEj4=", + "lastModified": 1765380834, + "narHash": "sha256-MUMk4DZ0V+gU7yee7DdiPwieRclS2uMNvLQGLWwew4M=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "e82b0773d332dc78ba550aa46227f21057cbaff8", + "rev": "bf83174d5ab54f384b1ec5068b3280241dbb849f", "type": "github" }, "original": { @@ -994,11 +994,11 @@ }, "nixpkgs_7": { "locked": { - "lastModified": 1764950072, - "narHash": "sha256-BmPWzogsG2GsXZtlT+MTcAWeDK5hkbGRZTeZNW42fwA=", + "lastModified": 1765186076, + "narHash": "sha256-hM20uyap1a0M9d344I692r+ik4gTMyj60cQWO+hAYP8=", "owner": "nixos", "repo": "nixpkgs", - "rev": "f61125a668a320878494449750330ca58b78c557", + "rev": "addf7cf5f383a3101ecfba091b98d0a1263dc9b8", "type": "github" }, "original": { @@ -1183,11 +1183,11 @@ ] }, "locked": { - "lastModified": 1736130495, - "narHash": "sha256-4i9nAJEZFv7vZMmrE0YG55I3Ggrtfo5/T07JEpEZ/RM=", + "lastModified": 1765361626, + "narHash": "sha256-kX0Dp/kYSRbQ+yd9e3lmmUWdNbipufvKfL2IzbrSpnY=", "owner": "snowfallorg", "repo": "lib", - "rev": "02d941739f98a09e81f3d2d9b3ab08918958beac", + "rev": "c566ad8b7352c30ec3763435de7c8f1c46ebb357", "type": "github" }, "original": { @@ -1254,11 +1254,11 @@ "tinted-zed": "tinted-zed" }, "locked": { - "lastModified": 1765047449, - "narHash": "sha256-VQcqjJ2g0kT9TW4ENwA2HBQJzfbCUd5s1Wm3K+R2QZY=", + "lastModified": 1765377959, + "narHash": "sha256-MsvpqrovI+iveyVam6sIPlSsUVVcmmhTxpD9w3OOsvw=", "owner": "nix-community", "repo": "stylix", - "rev": "bd00e01aab676aee88e6cc5c9238b4a5a7d6639a", + "rev": "54fcd2f342c6417548cc56f53e401224dcade639", "type": "github" }, "original": { @@ -1519,11 +1519,11 @@ ] }, "locked": { - "lastModified": 1765175766, - "narHash": "sha256-M4zs4bVUv0UNuVGspwwlcGs5FpCDt52LQBA5a9nj5Lg=", + "lastModified": 1765344150, + "narHash": "sha256-RoGBKQglbF19aINeV8F7DHCXxF7FrMRLgL2yjl9vOiQ=", "owner": "0xc000022070", "repo": "zen-browser-flake", - "rev": "5126a8426773dc213a8c0f0d646aca116194dab6", + "rev": "1adab25828578301037855c59849e9bbecf8948b", "type": "github" }, "original": { From ddf66697cb0fcbfab13536bd295f923d80dabaee Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Thu, 11 Dec 2025 08:32:28 +0100 Subject: [PATCH 014/108] chore: clean up code --- .just/machine.just | 2 +- .just/vars.just | 9 +++++---- modules/nixos/services/communication/matrix/default.nix | 2 -- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.just/machine.just b/.just/machine.just index ca10e1c..207185a 100644 --- a/.just/machine.just +++ b/.just/machine.just @@ -8,4 +8,4 @@ [no-exit-message] @update machine: just assert '-d "../systems/x86_64-linux/{{ machine }}"' "Machine {{ machine }} does not exist, must be one of: $(ls ../systems/x86_64-linux/ | sed ':a;N;$!ba;s/\n/, /g')" - nixos-rebuild switch -L --use-remote-sudo --target-host {{ machine }} --flake ..#{{ machine }} + nixos-rebuild switch -L --sudo --target-host {{ machine }} --flake ..#{{ machine }} diff --git a/.just/vars.just b/.just/vars.just index 3b706da..29a3e56 100644 --- a/.just/vars.just +++ b/.just/vars.just @@ -1,4 +1,5 @@ set unstable := true +set quiet := true base_path := invocation_directory() / "systems/x86_64-linux" @@ -8,14 +9,14 @@ base_path := invocation_directory() / "systems/x86_64-linux" sops := "sops" yq := "yq" -@_default: +_default: just --list [doc('list all vars of the target machine')] list machine: sops decrypt {{ base_path }}/{{ machine }}/secrets.yml -@edit machine: +edit machine: sops edit {{ base_path }}/{{ machine }}/secrets.yml @set machine key value: @@ -26,10 +27,10 @@ list machine: echo "Done" -@get machine key: +get machine key: sops decrypt {{ base_path }}/{{ machine }}/secrets.yml | yq ".$(echo "{{ key }}" | sed -E 's/\//./g')" -@remove machine key: +remove machine key: sops unset {{ base_path }}/{{ machine }}/secrets.yml "$(printf '%s\n' '["{{ key }}"]' | sed -E 's#/#"]["#g; s/\["([0-9]+)"\]/[\1]/g')" git add {{ base_path }}/{{ machine }}/secrets.yml diff --git a/modules/nixos/services/communication/matrix/default.nix b/modules/nixos/services/communication/matrix/default.nix index 6405932..c8a1f41 100644 --- a/modules/nixos/services/communication/matrix/default.nix +++ b/modules/nixos/services/communication/matrix/default.nix @@ -95,7 +95,6 @@ in { settings = { appservice = { provisioning.enabled = false; - # port = 40011; }; homeserver = { @@ -118,7 +117,6 @@ in { settings = { appservice = { provisioning.enabled = false; - # port = 40012; }; homeserver = { From c9be7ebb43d464a891112fb392bf97f931082213 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Thu, 11 Dec 2025 08:34:10 +0100 Subject: [PATCH 015/108] feat: add telegram bridge to matrix --- .../services/communication/matrix/default.nix | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/modules/nixos/services/communication/matrix/default.nix b/modules/nixos/services/communication/matrix/default.nix index c8a1f41..8bab5d0 100644 --- a/modules/nixos/services/communication/matrix/default.nix +++ b/modules/nixos/services/communication/matrix/default.nix @@ -110,6 +110,37 @@ in { }; }; + mautrix-telegram = { + enable = true; + registerToSynapse = true; + + settings = { + telegram = { + api_id = 32770816; + api_hash = "7b63778a976619c9d4ab62adc51cde79"; + bot_token = "disabled"; + + catch_up = true; + sequential_updates = false; + }; + + appservice = { + provisioning.enabled = false; + }; + + homeserver = { + address = "http://[::1]:${toString port}"; + domain = domain; + }; + + bridge = { + permissions = { + "@chris:${domain}" = "admin"; + }; + }; + }; + }; + mautrix-whatsapp = { enable = true; registerToSynapse = true; From c16cb15c10f4b6b4c2fe0276f7f142e3d8c1faaa Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 11 Dec 2025 07:35:00 +0000 Subject: [PATCH 016/108] chore(secrets): removed secret "kaas" from machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 3f34c21..976326e 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -10,7 +10,6 @@ forgejo: synapse: oidc_id: ENC[AES256_GCM,data:XbCpyGq0LeRJWq8dv/5Dipvp,iv:YDhgl26z1NBbIQLoLdGVz0+ze6o1ZcmgVHPfwoRj57I=,tag:y2vUuqnDmtTvVQmZCAlnLg==,type:str] oidc_secret: ENC[AES256_GCM,data:nVFi5EFbNMZ0mvrDHVYC0NiwJlo2eEw44D+Fcv9SKSb2oO00lGEDkP/oXDj5YgDq6RLQSe3f/SUOn77ntwnZYg==,iv:awe7VNUYOn9ofl1QlQTrEN5d0i5WkVM35qndruL4VXo=,tag:8Yoc9lFF9aWbtAa5fzQGEA==,type:str] -kaas: ENC[AES256_GCM,data:3yI6lH0rw+f2OFJ94Z7zb0pYwy4FDFs9rJi2wpd9VVWghmey5g4O788ypXa34XqKCQDDHDgTxwyDs6KpvCQQaLV1PDhXd4Po0SSlIOkUtCWhOf6Tp3PM2ASoE+AAAzJLJUc6AZdBJRyYU9V+UvO9jW+WmlpZpsg5crnVMzZo7f2AF0ep9A/A5BL1Y2UhYQE4LDVkLC9AL3hl8IhF5xSdZdO0ugrP0x7CKVUxA7fJyOjx7/IKVwvgKD4xlhIgv9lYPTvE2vUs+w==,iv:e6b98ZnBqf7hh3SSKGdTl63OpQm1oK95lHXdwTiLft8=,tag:IS/lDgvJvSd7OmDLP+uG1g==,type:str] radarr: apikey: ENC[AES256_GCM,data:G141GW4PyS5pbAV39HcVscMw3s30txOgTZzWaL7o+ccZfnfDLv796O6xKXdqGZ8saLsveghLw9Z6a5luusHyQ3Q5ESL6W7SVeZVTuSqSC3i/4jl75FJxhnsgVsfrnYxzLGpKiw==,iv:sZl/XLh6y3WgSAn6nH3sFB6atBifZdghm+QsCNDbcjY=,tag:Tw+R80nrF0T0yDti0Uf+ig==,type:str] sonarr: @@ -55,7 +54,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-12-10T12:55:31Z" - mac: ENC[AES256_GCM,data:qUZOcbHCssZC5Td1g9+TZFMccgHSDivTPF71+uGpyI88AuZAMt07kZuIuWcP4V8m633fl6WtmDAN/UP9IjbgBSUNLHpRcR5cOCAlxtLu0R0HNNIwMdxgseFTqPV/h/dMNSlEbAu06VZlt9S2CSkuTeyrP+GTpcskPJWF/RC50dk=,iv:QdwALTd3/1eKN7V3YtMf3Av0+XP6D59sQzDwqn7maOU=,tag:MX64z96lIn6RZBBsATi1ZQ==,type:str] + lastmodified: "2025-12-11T07:34:59Z" + mac: ENC[AES256_GCM,data:wEi918sFOHyo1QE50ce9CffDnxlno6UAGOGduM3GCR33LOsK/brPsQaV79k2EbLOdb2/vOy8A3SYAtmVs7s7tVIpukTyUjOLYL17Zu8DVKiQ5GHnJG+A564hj4kN4vS9fUStkpj+HiaBnkXUvIDRUGmXPkWhomwl8FvQca44ipk=,iv:bjAup4SJI62kQnjU0jzZMjwHJFJgkmtpp601rpl7aqM=,tag:aBrxrysJ/xCvEtM7OoJ1NA==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From 0fa3b79bd9b1b992ff2435530a5298db511e64da Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Thu, 11 Dec 2025 16:48:46 +0100 Subject: [PATCH 017/108] feat: jq just became a 1M times cooler! --- .jq/format.jq | 34 ++ .jq/table.jq | 59 +++ .just/users.just | 27 ++ .just/vars.just | 6 - .justfile | 3 + .../services/communication/matrix/default.nix | 3 +- .../nixos/services/media/mydia/default.nix | 11 +- .../nixos/services/media/servarr/default.nix | 39 +- sabnzbd.ini | 395 ++++++++++++++++++ 9 files changed, 568 insertions(+), 9 deletions(-) create mode 100644 .jq/format.jq create mode 100644 .jq/table.jq create mode 100644 .just/users.just create mode 100644 sabnzbd.ini diff --git a/.jq/format.jq b/.jq/format.jq new file mode 100644 index 0000000..5c65495 --- /dev/null +++ b/.jq/format.jq @@ -0,0 +1,34 @@ +def RESET: "0"; +def BOLD: "1"; +def DIM: "2"; +def ITALIC: "3"; +def UNDERLINE: "4"; +def BLINKING: "5"; +def INVERSE: "7"; +def HIDDEN: "8"; +def STRIKETHROUGH: "9"; +def RESET_FONT: "22"; + +def BLACK: 0; +def RED: 1; +def GREEN: 2; +def YELLOW: 3; +def BLUE: 4; +def MAGENTA: 5; +def CYAN: 6; +def WHITE: 7; +def DEFAULT: 9; + +def foreground(color): 30 + color; +def background(color): 40 + color; +def bright(color): 60 + color; + +def escape(options): + (if ((options|type) == "array") then options else [options] end) as $o + | "\u001b[\($o | map(tostring) | join(";"))m"; + +def style(options): escape(options) + . + escape([RESET]); + +def to_title: + (.|ascii_upcase) as $str + | escape([BOLD, foreground(BLACK), background(WHITE)]) + " " + $str + " " + escape([RESET]); diff --git a/.jq/table.jq b/.jq/table.jq new file mode 100644 index 0000000..83b98f2 --- /dev/null +++ b/.jq/table.jq @@ -0,0 +1,59 @@ +import "format" as _ {search:"./"}; + +def n_max(limit): + if . > limit then limit else . end; + +def n_min(limit): + if . < limit then limit else . end; + +def pad_right(width): + (. | tostring) as $s + | ($s | length) as $l + | ((width - $l) | n_min(0)) as $w + | ($s + (" " * $w)); + +def to_cells(sizes; fn): + to_entries + | map( + (sizes[.key]) as $size + | (" " + .value) + | pad_right($size + 2) + | fn // . + ); + +def to_cells(sizes): to_cells(sizes; null); + +def to_line(left; joiner; right): + [left, .[1], (.[1:] | map([joiner, .]) ), right] | flatten | join(""); + +def to_table(data; header_callback; cell_callback): + (data[0] | to_entries | map(.key)) as $keys + | ([$keys]) as $header + | (data | map(to_entries | map(.value))) as $rows + | ($header + $rows) as $cells + | ( + $keys # Use keys so that we have an array of the correct size + | to_entries + | map( + (.key) as $i + | $cells + | map(.[$i] | length) + | max + ) + ) as $column_sizes + | ( + [ + ($column_sizes | map("═" * (. + 2)) | to_line("β•”"; "β•€"; "β•—")), + ($keys | to_cells($column_sizes; header_callback) | to_line("β•‘"; "β”‚"; "β•‘")), + ($rows | map([ + ($column_sizes | map("─" * (. + 2)) | to_line("β•Ÿ"; "β”Ό"; "β•’")), + (. | to_cells($column_sizes; cell_callback) | to_line("β•‘"; "β”‚"; "β•‘")) + ])), + ($column_sizes | map("═" * (. + 2)) | to_line("β•š"; "β•§"; "╝")) + ] + | flatten + | join("\n") + ); + +def to_table(data; header_callback): to_table(data; header_callback; null); +def to_table(data): to_table(data; _::style(_::BOLD); null); diff --git a/.just/users.just b/.just/users.just new file mode 100644 index 0000000..cecd74b --- /dev/null +++ b/.just/users.just @@ -0,0 +1,27 @@ +set unstable := true +set quiet := true + +_default: + just --list + +[script] +list: + cd .. && just vars get ulmo zitadel/users \ + | jq fromjson \ + | jq -r -C ' + include ".jq/table"; + include ".jq/format"; + + to_entries + | sort_by(.key) + | map( + (.key|to_title) + ":\n" + + to_table( + .value + | to_entries + | sort_by(.key) + | map({username:.key} + .value) + ) + ) + | join("\n\nβ”„β”„β”„\n\n") + ' diff --git a/.just/vars.just b/.just/vars.just index 29a3e56..230f00c 100644 --- a/.just/vars.just +++ b/.just/vars.just @@ -3,12 +3,6 @@ set quiet := true base_path := invocation_directory() / "systems/x86_64-linux" -# sops := "nix shell nixpkgs#sops --command sops" -# yq := "nix shell nixpkgs#yq --command yq" - -sops := "sops" -yq := "yq" - _default: just --list diff --git a/.justfile b/.justfile index 75537e1..cee0db9 100644 --- a/.justfile +++ b/.justfile @@ -4,6 +4,9 @@ [doc('Manage vars')] mod vars '.just/vars.just' +[doc('Manage users')] +mod users '.just/users.just' + [doc('Manage machines')] mod machine '.just/machine.just' diff --git a/modules/nixos/services/communication/matrix/default.nix b/modules/nixos/services/communication/matrix/default.nix index 8bab5d0..33af8e4 100644 --- a/modules/nixos/services/communication/matrix/default.nix +++ b/modules/nixos/services/communication/matrix/default.nix @@ -121,10 +121,11 @@ in { bot_token = "disabled"; catch_up = true; - sequential_updates = false; + sequential_updates = true; }; appservice = { + port = 40011; provisioning.enabled = false; }; diff --git a/modules/nixos/services/media/mydia/default.nix b/modules/nixos/services/media/mydia/default.nix index 2bee38a..7e082a3 100644 --- a/modules/nixos/services/media/mydia/default.nix +++ b/modules/nixos/services/media/mydia/default.nix @@ -36,7 +36,7 @@ in { # uri = "file:///var/lib/mydia/mydia.db"; type = "postgres"; uri = "postgres://mydia@localhost:5432/mydia?sslmode=disable"; - passwordFile = config.sops.secrets."mydia/qbittorrent_password".path; + passwordFile = config.sops.templates."mydia/database_password".path; }; secretKeyBaseFile = config.sops.secrets."mydia/secret_key_base".path; @@ -82,5 +82,14 @@ in { key = "qbittorrent/password"; }; }; + + sops.templates."mydia/database_password" = { + owner = config.services.mydia.user; + group = config.services.mydia.group; + restartUnits = ["mydia.service"]; + content = '' + DATABASE_PASSWORD="" + ''; + }; }; } diff --git a/modules/nixos/services/media/servarr/default.nix b/modules/nixos/services/media/servarr/default.nix index c09e66f..bb90352 100644 --- a/modules/nixos/services/media/servarr/default.nix +++ b/modules/nixos/services/media/servarr/default.nix @@ -96,7 +96,8 @@ in { sabnzbd = { enable = true; openFirewall = true; - configFile = config.sops.templates."sabnzbd/config.ini".path; + configFile = "/var/media/sabnzbd/config.ini"; + # configFile = config.sops.templates."sabnzbd/config.ini".path; user = "sabnzbd"; group = "media"; @@ -301,12 +302,48 @@ in { })) |> lib.concat [ { + secrets = { + "sabnzbd/apikey" = {}; + "sabnzbd/sunnyweb/username" = {}; + "sabnzbd/sunnyweb/password" = {}; + }; + templates = { "sabnzbd/config.ini" = { owner = "sabnzbd"; group = "media"; + mode = "0660"; content = '' + __version__ = 19 + __encoding__ = utf-8 + [misc] + download_dir = /var/media/downloads/incomplete + complete_dir = /var/media/downloads/done + api_key = ${config.sops.placeholder."sabnzbd/apikey"} + log_dir = logs + [servers] + [[news.sunnyusenet.com]] + name = news.sunnyusenet.com + displayname = news.sunnyusenet.com + host = news.sunnyusenet.com + port = 563 + timeout = 60 + username = ${config.sops.placeholder."sabnzbd/sunnyweb/username"} + password = ${config.sops.placeholder."sabnzbd/sunnyweb/password"} + connections = 8 + ssl = 1 + ssl_verify = 3 + ssl_ciphers = "" + enable = 1 + required = 0 + optional = 0 + retention = 0 + expire_date = "" + quota = "" + usage_at_start = 0 + priority = 1 + notes = "" ''; }; }; diff --git a/sabnzbd.ini b/sabnzbd.ini new file mode 100644 index 0000000..fd60f57 --- /dev/null +++ b/sabnzbd.ini @@ -0,0 +1,395 @@ +__version__ = 19 +__encoding__ = utf-8 +[misc] +helpful_warnings = 1 +queue_complete = hibernate_pc +queue_complete_pers = 0 +bandwidth_perc = 100 +refresh_rate = 1 +interface_settings = '{"dateFormat":"YYYY-MM-DD HH:mm","extraQueueColumns":[],"extraHistoryColumns":[],"displayCompact":false,"displayFullWidth":false,"confirmDeleteQueue":true,"confirmDeleteHistory":true,"keyboardShortcuts":true}' +queue_limit = 20 +config_lock = 0 +fixed_ports = 1 +notified_new_skin = 2 +direct_unpack_tested = 1 +sorters_converted = 1 +check_new_rel = 1 +auto_browser = 0 +language = en +enable_https_verification = 0 +host = 0.0.0.0 +port = 8080 +https_port = "" +username = "" +password = "" +bandwidth_max = "" +cache_limit = 1G +web_dir = Glitter +web_color = Auto +https_cert = server.cert +https_key = server.key +https_chain = "" +enable_https = 0 +inet_exposure = 0 +api_key = 0052eba0db9d4b4f93a8a96f0cb85198 +nzb_key = 171ebeb3e0044c379dc7719bef6b3144 +socks5_proxy_url = "" +permissions = "" +download_dir = /var/media/downloads/incomplete +download_free = "" +complete_dir = /var/media/downloads/done +complete_free = "" +fulldisk_autoresume = 0 +script_dir = "" +nzb_backup_dir = "" +admin_dir = admin +backup_dir = "" +dirscan_dir = "" +dirscan_speed = 5 +password_file = "" +log_dir = logs +max_art_tries = 3 +top_only = 0 +sfv_check = 1 +script_can_fail = 0 +enable_recursive = 1 +flat_unpack = 0 +par_option = "" +pre_check = 0 +nice = "" +win_process_prio = 3 +ionice = "" +fail_hopeless_jobs = 1 +fast_fail = 1 +auto_disconnect = 1 +pre_script = None +end_queue_script = None +no_dupes = 0 +no_series_dupes = 0 +no_smart_dupes = 0 +dupes_propercheck = 1 +pause_on_pwrar = 1 +ignore_samples = 0 +deobfuscate_final_filenames = 1 +auto_sort = "" +direct_unpack = 0 +propagation_delay = 0 +folder_rename = 1 +replace_spaces = 0 +replace_underscores = 0 +replace_dots = 0 +safe_postproc = 1 +pause_on_post_processing = 0 +enable_all_par = 0 +sanitize_safe = 0 +cleanup_list = , +unwanted_extensions = , +action_on_unwanted_extensions = 0 +unwanted_extensions_mode = 0 +new_nzb_on_failure = 0 +history_retention = "" +history_retention_option = all +history_retention_number = 1 +quota_size = "" +quota_day = "" +quota_resume = 0 +quota_period = m +enable_tv_sorting = 0 +tv_sort_string = "" +tv_categories = tv, +enable_movie_sorting = 0 +movie_sort_string = "" +movie_sort_extra = -cd%1 +movie_categories = movies, +enable_date_sorting = 0 +date_sort_string = "" +date_categories = tv, +schedlines = , +rss_rate = 60 +ampm = 0 +start_paused = 0 +preserve_paused_state = 0 +enable_par_cleanup = 1 +process_unpacked_par2 = 1 +enable_multipar = 1 +enable_unrar = 1 +enable_7zip = 1 +enable_filejoin = 1 +enable_tsjoin = 1 +overwrite_files = 0 +ignore_unrar_dates = 0 +backup_for_duplicates = 0 +empty_postproc = 0 +wait_for_dfolder = 0 +rss_filenames = 0 +api_logging = 1 +html_login = 1 +warn_dupl_jobs = 0 +keep_awake = 1 +tray_icon = 1 +allow_incomplete_nzb = 0 +enable_broadcast = 1 +ipv6_hosting = 0 +ipv6_staging = 0 +api_warnings = 1 +no_penalties = 0 +x_frame_options = 1 +allow_old_ssl_tls = 0 +enable_season_sorting = 1 +verify_xff_header = 0 +rss_odd_titles = nzbindex.nl/, nzbindex.com/, nzbclub.com/ +quick_check_ext_ignore = nfo, sfv, srr +req_completion_rate = 100.2 +selftest_host = self-test.sabnzbd.org +movie_rename_limit = 100M +episode_rename_limit = 20M +size_limit = 0 +direct_unpack_threads = 3 +history_limit = 5 +wait_ext_drive = 5 +max_foldername_length = 246 +nomedia_marker = "" +ipv6_servers = 1 +url_base = /sabnzbd +host_whitelist = usenet.kruining.eu, ulmo +local_ranges = , +max_url_retries = 10 +downloader_sleep_time = 10 +receive_threads = 2 +switchinterval = 0.005 +ssdp_broadcast_interval = 15 +ext_rename_ignore = , +email_server = "" +email_to = , +email_from = "" +email_account = "" +email_pwd = "" +email_endjob = 0 +email_full = 0 +email_dir = "" +email_rss = 0 +email_cats = *, +config_conversion_version = 4 +disable_par2cmdline = 0 +disable_archive = 0 +unrar_parameters = "" +outgoing_nntp_ip = "" +[logging] +log_level = 1 +max_log_size = 5242880 +log_backups = 5 +[ncenter] +ncenter_enable = 0 +ncenter_cats = *, +ncenter_prio_startup = 0 +ncenter_prio_download = 0 +ncenter_prio_pause_resume = 0 +ncenter_prio_pp = 0 +ncenter_prio_complete = 1 +ncenter_prio_failed = 1 +ncenter_prio_disk_full = 1 +ncenter_prio_new_login = 0 +ncenter_prio_warning = 0 +ncenter_prio_error = 0 +ncenter_prio_queue_done = 0 +ncenter_prio_other = 1 +ncenter_prio_quota = 1 +[acenter] +acenter_enable = 0 +acenter_cats = *, +acenter_prio_startup = 0 +acenter_prio_download = 0 +acenter_prio_pause_resume = 0 +acenter_prio_pp = 0 +acenter_prio_complete = 1 +acenter_prio_failed = 1 +acenter_prio_disk_full = 1 +acenter_prio_new_login = 0 +acenter_prio_warning = 0 +acenter_prio_error = 0 +acenter_prio_queue_done = 0 +acenter_prio_other = 1 +acenter_prio_quota = 1 +[ntfosd] +ntfosd_enable = 1 +ntfosd_cats = *, +ntfosd_prio_startup = 0 +ntfosd_prio_download = 0 +ntfosd_prio_pause_resume = 0 +ntfosd_prio_pp = 0 +ntfosd_prio_complete = 1 +ntfosd_prio_failed = 1 +ntfosd_prio_disk_full = 1 +ntfosd_prio_new_login = 0 +ntfosd_prio_warning = 0 +ntfosd_prio_error = 0 +ntfosd_prio_queue_done = 0 +ntfosd_prio_other = 1 +ntfosd_prio_quota = 1 +[prowl] +prowl_enable = 0 +prowl_cats = *, +prowl_apikey = "" +prowl_prio_startup = -3 +prowl_prio_download = -3 +prowl_prio_pause_resume = -3 +prowl_prio_pp = -3 +prowl_prio_complete = 0 +prowl_prio_failed = 1 +prowl_prio_disk_full = 1 +prowl_prio_new_login = -3 +prowl_prio_warning = -3 +prowl_prio_error = -3 +prowl_prio_queue_done = -3 +prowl_prio_other = 0 +prowl_prio_quota = 0 +[pushover] +pushover_token = "" +pushover_userkey = "" +pushover_device = "" +pushover_emergency_expire = 3600 +pushover_emergency_retry = 60 +pushover_enable = 0 +pushover_cats = *, +pushover_prio_startup = -3 +pushover_prio_download = -2 +pushover_prio_pause_resume = -2 +pushover_prio_pp = -3 +pushover_prio_complete = -1 +pushover_prio_failed = -1 +pushover_prio_disk_full = 1 +pushover_prio_new_login = -3 +pushover_prio_warning = 1 +pushover_prio_error = 1 +pushover_prio_queue_done = -3 +pushover_prio_other = -1 +pushover_prio_quota = -1 +[pushbullet] +pushbullet_enable = 0 +pushbullet_cats = *, +pushbullet_apikey = "" +pushbullet_device = "" +pushbullet_prio_startup = 0 +pushbullet_prio_download = 0 +pushbullet_prio_pause_resume = 0 +pushbullet_prio_pp = 0 +pushbullet_prio_complete = 1 +pushbullet_prio_failed = 1 +pushbullet_prio_disk_full = 1 +pushbullet_prio_new_login = 0 +pushbullet_prio_warning = 0 +pushbullet_prio_error = 0 +pushbullet_prio_queue_done = 0 +pushbullet_prio_other = 1 +pushbullet_prio_quota = 1 +[apprise] +apprise_enable = 0 +apprise_cats = *, +apprise_urls = "" +apprise_target_startup = "" +apprise_target_startup_enable = 0 +apprise_target_download = "" +apprise_target_download_enable = 0 +apprise_target_pause_resume = "" +apprise_target_pause_resume_enable = 0 +apprise_target_pp = "" +apprise_target_pp_enable = 0 +apprise_target_complete = "" +apprise_target_complete_enable = 1 +apprise_target_failed = "" +apprise_target_failed_enable = 1 +apprise_target_disk_full = "" +apprise_target_disk_full_enable = 0 +apprise_target_new_login = "" +apprise_target_new_login_enable = 1 +apprise_target_warning = "" +apprise_target_warning_enable = 0 +apprise_target_error = "" +apprise_target_error_enable = 0 +apprise_target_queue_done = "" +apprise_target_queue_done_enable = 0 +apprise_target_other = "" +apprise_target_other_enable = 1 +apprise_target_quota = "" +apprise_target_quota_enable = 1 +[nscript] +nscript_enable = 0 +nscript_cats = *, +nscript_script = "" +nscript_parameters = "" +nscript_prio_startup = 0 +nscript_prio_download = 0 +nscript_prio_pause_resume = 0 +nscript_prio_pp = 0 +nscript_prio_complete = 1 +nscript_prio_failed = 1 +nscript_prio_disk_full = 1 +nscript_prio_new_login = 0 +nscript_prio_warning = 0 +nscript_prio_error = 0 +nscript_prio_queue_done = 0 +nscript_prio_other = 1 +nscript_prio_quota = 1 +[categories] +[[*]] +name = * +order = 0 +pp = 3 +script = None +dir = "" +newzbin = "" +priority = 0 +[[movies]] +name = movies +order = 1 +pp = "" +script = Default +dir = "" +newzbin = "" +priority = -100 +[[tv]] +name = tv +order = 2 +pp = "" +script = Default +dir = "" +newzbin = "" +priority = -100 +[[audio]] +name = audio +order = 3 +pp = "" +script = Default +dir = "" +newzbin = "" +priority = -100 +[[software]] +name = software +order = 4 +pp = "" +script = Default +dir = "" +newzbin = "" +priority = -100 +[servers] +[[news.sunnyusenet.com]] +name = news.sunnyusenet.com +displayname = news.sunnyusenet.com +host = news.sunnyusenet.com +port = 563 +timeout = 60 +username = michiel@hazelhof.nl +password = dasusenet +connections = 8 +ssl = 1 +ssl_verify = 3 +ssl_ciphers = "" +enable = 1 +required = 0 +optional = 0 +retention = 0 +expire_date = "" +quota = "" +usage_at_start = 0 +priority = 1 +notes = "" From 4f5b2372d5237ba7ee538f33aed43a81c67a4b64 Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 11 Dec 2025 21:13:42 +0000 Subject: [PATCH 018/108] chore(secrets): set secret "zitadel/users" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 976326e..255ae2e 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -4,7 +4,7 @@ email: zitadel: masterKey: ENC[AES256_GCM,data:4MPvBo407qrS7NF4oUTf84tZoPkSRmiHdD7qpkYeHME=,iv:H2NIAN0xBUDqnyco9gA3zYAsKtSeA/JpqYrPhc1eqc0=,tag:6OFGDfsucG5gDerImgpuXA==,type:str] nix: {} - users: ENC[AES256_GCM,data:xkjm0+PBt6gmZyfi3n3OIEe5b+d4OtN0Y3UfmdcbcJHbJZuiz+60oUjlAN0vjtsi0muufoAqtGJTIpm9nDZzzN7b7LK43TAhcuSlIm5LpbZFp1U3H4laRbTwauAT6wA0aDCfAkwTozxAuEUk1jAu+65ktJNJb7b0PR7s/I/wf7IgW2+K4Jv3LIOZIipUwfuvXuTzsxCElYRvGZXmIuXrYq1EaymksHHggemrKeMWLAae7mzz5v3aBbwxiVjQNkQkS4ApsO/5nZUat0oqXA==,iv:fptZn4NmX3iYKSEPLJAOFpt+KQ6TR1w9KaY9IF4p/Wk=,tag:UKvMOSIT5/mhfZA3usbLhQ==,type:str] + users: ENC[AES256_GCM,data:J8BydII0eLW7gPo2orNS8VQ/YuxqGKtyXiW5CWtoJJY5EN6CtcmSTPCJB5eftBNxnTZy3RNmYp8OYdD8TE5G1BhmizUsEQv7lrbO5R7p4FuMxeix0bi3hRcBtpv6gOLPjC/V3xs4gIX6hCm+2zOW9k/9e0K30TDTN2PEfwmAV8bOSu5oV6jxvMogu2MJ4sXR+RTmrURVg6hu0IC2m7j9RUExG0HDoZlEWKKDWm2KLncd135s5bEh9qXLCGTTZHPsK+9tp38jXxSEs/eHEmCKAHMrE5ZYUkPQLxsAnbfe34kMYAiM/97fPWwDuQpK7wG2eG+y1HbxbzJCVp1KYftcDXpnMSVYmBc=,iv:+bSmAeoKuxaDrx/2H4/uuwNx+M5swzqRnL7AyYuR49k=,tag:KM7OI6oHME2YosGixHvCQw==,type:str] forgejo: action_runner_token: ENC[AES256_GCM,data:yJ6OnRq5kinbuhvH06K5o3l86EafuBoojMwg/qhP+cgeH+BwPeE+Ng==,iv:IeXJahPxgLNIUFmkgp495tLVh8UyQBmJ2SnVEUhlhHs=,tag:XYQi613CxSp8AQeilJMrsg==,type:str] synapse: @@ -54,7 +54,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-12-11T07:34:59Z" - mac: ENC[AES256_GCM,data:wEi918sFOHyo1QE50ce9CffDnxlno6UAGOGduM3GCR33LOsK/brPsQaV79k2EbLOdb2/vOy8A3SYAtmVs7s7tVIpukTyUjOLYL17Zu8DVKiQ5GHnJG+A564hj4kN4vS9fUStkpj+HiaBnkXUvIDRUGmXPkWhomwl8FvQca44ipk=,iv:bjAup4SJI62kQnjU0jzZMjwHJFJgkmtpp601rpl7aqM=,tag:aBrxrysJ/xCvEtM7OoJ1NA==,type:str] + lastmodified: "2025-12-11T21:13:41Z" + mac: ENC[AES256_GCM,data:TK1gJF2n9C9ja/ubPlDy8DCAqG12KqvyxTD6eVJ69fdApYC6B1nLW0FHV7VEqHQOlAhN66RfVhARIl61YCG2UC66IijO2s37tDKpyQOpZUGNf3s4kipwq9SD2zBMletF2SkggqURiBReeVdVxBgPEnPi3uKBLSJ1UVZSlnc43bY=,iv:WU2eTHYPYnREcOcqClqqj1oOrBE2ijNtNwshz7hpdQ8=,tag:Gt6cdlQEhYm8kg/hdhxYTg==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From 6641632319a7e15ceaf84c8136876de0c7d52c84 Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 11 Dec 2025 21:14:40 +0000 Subject: [PATCH 019/108] chore(secrets): set secret "zitadel/users" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 255ae2e..39a5b5a 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -54,7 +54,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-12-11T21:13:41Z" - mac: ENC[AES256_GCM,data:TK1gJF2n9C9ja/ubPlDy8DCAqG12KqvyxTD6eVJ69fdApYC6B1nLW0FHV7VEqHQOlAhN66RfVhARIl61YCG2UC66IijO2s37tDKpyQOpZUGNf3s4kipwq9SD2zBMletF2SkggqURiBReeVdVxBgPEnPi3uKBLSJ1UVZSlnc43bY=,iv:WU2eTHYPYnREcOcqClqqj1oOrBE2ijNtNwshz7hpdQ8=,tag:Gt6cdlQEhYm8kg/hdhxYTg==,type:str] + lastmodified: "2025-12-11T21:14:39Z" + mac: ENC[AES256_GCM,data:IdJz6m8YtzyB5PptPBceFuTQH2KLoS1RQSKiXuAQyjSqibOljtAgisixOxbPzjgKij8OkRWxQuNdlLcSFt7RAf13HPlGh1U2tl+zzsgYyKGkOiQql8kmfWzI0RaPsVHOPeM0CJHcPMJs/K+T1QN5H/OlIuMim5/shLkLImTwb54=,iv:Zxz6vs8gJJA1eGgv9wusDz/45R5r0/Da6Eg3lbzqF80=,tag:5ivhveUm4cPMUHT91uthjQ==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From 769bb3d3d0442baeccd0145561d54171542060a5 Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 11 Dec 2025 21:26:03 +0000 Subject: [PATCH 020/108] chore(secrets): set secret "zitadel/users" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 39a5b5a..741511c 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -54,7 +54,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-12-11T21:14:39Z" - mac: ENC[AES256_GCM,data:IdJz6m8YtzyB5PptPBceFuTQH2KLoS1RQSKiXuAQyjSqibOljtAgisixOxbPzjgKij8OkRWxQuNdlLcSFt7RAf13HPlGh1U2tl+zzsgYyKGkOiQql8kmfWzI0RaPsVHOPeM0CJHcPMJs/K+T1QN5H/OlIuMim5/shLkLImTwb54=,iv:Zxz6vs8gJJA1eGgv9wusDz/45R5r0/Da6Eg3lbzqF80=,tag:5ivhveUm4cPMUHT91uthjQ==,type:str] + lastmodified: "2025-12-11T21:26:03Z" + mac: ENC[AES256_GCM,data:9HIMBsePObNInn83qMDRX39mZ9qrjYCBDWVXcHB2Ws9I3ag5qp1wM/DmtXTI2tuMqS6op3xw0FgwzK2rkO+UHiWOKWBhiI5PQlw15J0WaWS4mDDqc/L8nGwMrdFF1SVJlkM5gk1IriOXgOcsH3nIrdTgL5+6u5W46VMA1DJ23Xw=,iv:0HbK6wIm/CewWX8ppojMRaZMVRcBGOPMWorLFJUBWQk=,tag:sNBm3YeYEQuFuuFxFzR32g==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From 5819545a8b02c9c41b3f728865070997b7ba5716 Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 11 Dec 2025 21:32:24 +0000 Subject: [PATCH 021/108] chore(secrets): set secret "zitadel/users" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 741511c..c36cd43 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -54,7 +54,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-12-11T21:26:03Z" - mac: ENC[AES256_GCM,data:9HIMBsePObNInn83qMDRX39mZ9qrjYCBDWVXcHB2Ws9I3ag5qp1wM/DmtXTI2tuMqS6op3xw0FgwzK2rkO+UHiWOKWBhiI5PQlw15J0WaWS4mDDqc/L8nGwMrdFF1SVJlkM5gk1IriOXgOcsH3nIrdTgL5+6u5W46VMA1DJ23Xw=,iv:0HbK6wIm/CewWX8ppojMRaZMVRcBGOPMWorLFJUBWQk=,tag:sNBm3YeYEQuFuuFxFzR32g==,type:str] + lastmodified: "2025-12-11T21:32:23Z" + mac: ENC[AES256_GCM,data:Y4K10+NXrZOPVRz1+IPcCF9yqJ0bqoGG5TwHeqZ/MpjSwwOXnPLJojF9L+3BnN2EWvIf+Zex8UsLqQVPPkUef2eI/FcNfcjuOe1OVsNOr6Z05Wkouo5j5qwEpKIMEMNzrI1iG6460UpJ6sZBvGTaSd5ShDMt21fqHy2NhUfWDtA=,iv:2z3kA3pRz51wl5ZRp1BORwfERXGaOizroWHXMdztTbI=,tag:4rAYtmk3u9MXmAEn2QnBrw==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From 1610ab1ca00f2e52ec05a6afd6bd5744ecd59073 Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 11 Dec 2025 21:43:37 +0000 Subject: [PATCH 022/108] chore(secrets): set secret "zitadel/users" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index c36cd43..b7c0cc0 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -4,7 +4,7 @@ email: zitadel: masterKey: ENC[AES256_GCM,data:4MPvBo407qrS7NF4oUTf84tZoPkSRmiHdD7qpkYeHME=,iv:H2NIAN0xBUDqnyco9gA3zYAsKtSeA/JpqYrPhc1eqc0=,tag:6OFGDfsucG5gDerImgpuXA==,type:str] nix: {} - users: ENC[AES256_GCM,data:J8BydII0eLW7gPo2orNS8VQ/YuxqGKtyXiW5CWtoJJY5EN6CtcmSTPCJB5eftBNxnTZy3RNmYp8OYdD8TE5G1BhmizUsEQv7lrbO5R7p4FuMxeix0bi3hRcBtpv6gOLPjC/V3xs4gIX6hCm+2zOW9k/9e0K30TDTN2PEfwmAV8bOSu5oV6jxvMogu2MJ4sXR+RTmrURVg6hu0IC2m7j9RUExG0HDoZlEWKKDWm2KLncd135s5bEh9qXLCGTTZHPsK+9tp38jXxSEs/eHEmCKAHMrE5ZYUkPQLxsAnbfe34kMYAiM/97fPWwDuQpK7wG2eG+y1HbxbzJCVp1KYftcDXpnMSVYmBc=,iv:+bSmAeoKuxaDrx/2H4/uuwNx+M5swzqRnL7AyYuR49k=,tag:KM7OI6oHME2YosGixHvCQw==,type:str] + users: ENC[AES256_GCM,data:PU1JLhErx3dGQHC8nwph+Kz86T5IlU/pN7aScECKSxt6QJoHtpdTdJzEUlfpHN9cshZXBvZLvK8E+bFunS4UEMqhd557dpr7yj0VKIfrMOV+NmWZ4Jp2KX6R+HSV+LC6Tym5TcRJ6MCyYHB9tnxlukI556bFWpGrjYaxwlvn/4lCAlvCW/4HYjnDsj9ILQ1OLy0w4jMFz0hPUjRoQ/w6c8rKGDIa4HTwxNDr+J6LhkoaewdTT1Z8+g/vTN7JaPJ5dknLliJLtsA0q/czSUw7bCqGRpDSZ2tcAgFaFpyTArAZJHeMqjqvKrqP1mzoVrandVYbERXaQqKy0FAtzMl22ddSNT6V8gEVrgHuf99LlPOIM5hEB5Vs3HDvSVsdCp0pAyee42jHPiaUfN9kyI5r1tZjWAg=,iv:bo4jp4nCDBaZWr7RdKAczbrnkv1+itpXlobVwHqJi2s=,tag:edLXxKbRSr9KU3CixFYECg==,type:str] forgejo: action_runner_token: ENC[AES256_GCM,data:yJ6OnRq5kinbuhvH06K5o3l86EafuBoojMwg/qhP+cgeH+BwPeE+Ng==,iv:IeXJahPxgLNIUFmkgp495tLVh8UyQBmJ2SnVEUhlhHs=,tag:XYQi613CxSp8AQeilJMrsg==,type:str] synapse: @@ -54,7 +54,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-12-11T21:32:23Z" - mac: ENC[AES256_GCM,data:Y4K10+NXrZOPVRz1+IPcCF9yqJ0bqoGG5TwHeqZ/MpjSwwOXnPLJojF9L+3BnN2EWvIf+Zex8UsLqQVPPkUef2eI/FcNfcjuOe1OVsNOr6Z05Wkouo5j5qwEpKIMEMNzrI1iG6460UpJ6sZBvGTaSd5ShDMt21fqHy2NhUfWDtA=,iv:2z3kA3pRz51wl5ZRp1BORwfERXGaOizroWHXMdztTbI=,tag:4rAYtmk3u9MXmAEn2QnBrw==,type:str] + lastmodified: "2025-12-11T21:43:36Z" + mac: ENC[AES256_GCM,data:DfOJESY42ZRDcrWYWKESRjYx9v3A+tX97dyfVxd+nJUbg3fxirc2ixLNedFG4qiw/O3C2HxikaOOmIntDSTB6iziXfrcgjsRB5fLJ7pyZG0sHl27n/FiNs/MUMd0eJmsiWzujxmYFauozMCuIYX+IjmzSs1k61Bk9OXEjuA+sVM=,iv:fX0fyMQsk6AMiYj6QpaBlxMtVpwnwac2omOMCp3nV1A=,tag:Zq5ggo/t83Ivw3i4ehGZgA==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From 9247e5c24837cac3ac73a2ed5316bd7d767bf45a Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 11 Dec 2025 21:50:32 +0000 Subject: [PATCH 023/108] chore(secrets): set secret "zitadel/users" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index b7c0cc0..b7f7e4e 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -54,7 +54,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-12-11T21:43:36Z" - mac: ENC[AES256_GCM,data:DfOJESY42ZRDcrWYWKESRjYx9v3A+tX97dyfVxd+nJUbg3fxirc2ixLNedFG4qiw/O3C2HxikaOOmIntDSTB6iziXfrcgjsRB5fLJ7pyZG0sHl27n/FiNs/MUMd0eJmsiWzujxmYFauozMCuIYX+IjmzSs1k61Bk9OXEjuA+sVM=,iv:fX0fyMQsk6AMiYj6QpaBlxMtVpwnwac2omOMCp3nV1A=,tag:Zq5ggo/t83Ivw3i4ehGZgA==,type:str] + lastmodified: "2025-12-11T21:50:31Z" + mac: ENC[AES256_GCM,data:abjGTh1BS2n4DYtH8WvSUIsYtVkOVjcJKzIRYhxRi7WzP5LPJroYXL+jgdbr8Ryt+8s2AIZshRnbxitwzfKf3mx6qVQ5pK8+e7C/+sCMHnbQDXf3Z6OKSElqJMT4T5dZBbUj+64lbKM0dbnQLTMHNwjqDmtA9Xn7dCgjLjS+yZ8=,iv:AKyUqCOkoapTpFEK7FZpoDGuIRIqb9SHo2BH2vDy9Ms=,tag:GLQenq7VeIVC9Ir5fwW9Lg==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From 2e9c79b6f069465585cdb8fbec32cb299f08989e Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 11 Dec 2025 21:51:00 +0000 Subject: [PATCH 024/108] chore(secrets): set secret "zitadel/users" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index b7f7e4e..e644d84 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -54,7 +54,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-12-11T21:50:31Z" - mac: ENC[AES256_GCM,data:abjGTh1BS2n4DYtH8WvSUIsYtVkOVjcJKzIRYhxRi7WzP5LPJroYXL+jgdbr8Ryt+8s2AIZshRnbxitwzfKf3mx6qVQ5pK8+e7C/+sCMHnbQDXf3Z6OKSElqJMT4T5dZBbUj+64lbKM0dbnQLTMHNwjqDmtA9Xn7dCgjLjS+yZ8=,iv:AKyUqCOkoapTpFEK7FZpoDGuIRIqb9SHo2BH2vDy9Ms=,tag:GLQenq7VeIVC9Ir5fwW9Lg==,type:str] + lastmodified: "2025-12-11T21:50:59Z" + mac: ENC[AES256_GCM,data:O3I8dUG3JgfhDRC8V4gYjyACfXL/u8kuV0G31yz0qu2J3wkSI0tq3MR4+oZXFxDt8YkMevCUuQFZF4faYyTeFcdwRKlev0WexH1dWT22DUm+tkmcg2R1tv7TI/US6GTlXu/g9WGrLkHZjh1YQZ9AUNxtwL6O/CaKOuiR2nmmtYE=,iv:ojDH8+YWJQK3Rp6dHwj1T+/SVltnFDKKsOmYyeQpoMc=,tag:ieca6xM/sw7pjpu8czdv0A==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From 174b85a3e2c0582e3f4d32da50fb422f0a2a9920 Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 11 Dec 2025 22:11:51 +0000 Subject: [PATCH 025/108] chore(secrets): set secret "zitadel/users" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index e644d84..8a8b744 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -4,7 +4,7 @@ email: zitadel: masterKey: ENC[AES256_GCM,data:4MPvBo407qrS7NF4oUTf84tZoPkSRmiHdD7qpkYeHME=,iv:H2NIAN0xBUDqnyco9gA3zYAsKtSeA/JpqYrPhc1eqc0=,tag:6OFGDfsucG5gDerImgpuXA==,type:str] nix: {} - users: ENC[AES256_GCM,data:PU1JLhErx3dGQHC8nwph+Kz86T5IlU/pN7aScECKSxt6QJoHtpdTdJzEUlfpHN9cshZXBvZLvK8E+bFunS4UEMqhd557dpr7yj0VKIfrMOV+NmWZ4Jp2KX6R+HSV+LC6Tym5TcRJ6MCyYHB9tnxlukI556bFWpGrjYaxwlvn/4lCAlvCW/4HYjnDsj9ILQ1OLy0w4jMFz0hPUjRoQ/w6c8rKGDIa4HTwxNDr+J6LhkoaewdTT1Z8+g/vTN7JaPJ5dknLliJLtsA0q/czSUw7bCqGRpDSZ2tcAgFaFpyTArAZJHeMqjqvKrqP1mzoVrandVYbERXaQqKy0FAtzMl22ddSNT6V8gEVrgHuf99LlPOIM5hEB5Vs3HDvSVsdCp0pAyee42jHPiaUfN9kyI5r1tZjWAg=,iv:bo4jp4nCDBaZWr7RdKAczbrnkv1+itpXlobVwHqJi2s=,tag:edLXxKbRSr9KU3CixFYECg==,type:str] + users: ENC[AES256_GCM,data:quxYk+XT5VZy+holUr3g5ycI34Z4BfSp2eKK4CZYBvl5ZES96Jf/oXCAWXhlEpXiKwsvKkAZNBdwLaqWrzRJJBzEi2UwZJfX1I0vDtWMz4VN5mKtzr+Vavty4visrleS46w2O/xg1PzOt/gv2CilyQmcBlpMbhLYVj4A9rbpiaIqXaYB/JMpa0sUyjjs/lxFDugv3pVEvmr7b0Gjqb1+A3TldEDxBT+P10jeomDoVJbMFyF09dpSQZrTlhyDHE742armspwvQyiKjmxmpkK1+L9iRgcBFiKCbkx4aZq5uvh3lNlVbsFqWhBbBRIOMjdgOm9OmQ7FGqqMSihsW6APMi9KTgpWUpk=,iv:mxtamxo8DWaxafC9AsgHKxcqNp5mLOEiPetoHZeA95c=,tag:ITNi8tMqkDxaONEKOYU/UQ==,type:str] forgejo: action_runner_token: ENC[AES256_GCM,data:yJ6OnRq5kinbuhvH06K5o3l86EafuBoojMwg/qhP+cgeH+BwPeE+Ng==,iv:IeXJahPxgLNIUFmkgp495tLVh8UyQBmJ2SnVEUhlhHs=,tag:XYQi613CxSp8AQeilJMrsg==,type:str] synapse: @@ -54,7 +54,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-12-11T21:50:59Z" - mac: ENC[AES256_GCM,data:O3I8dUG3JgfhDRC8V4gYjyACfXL/u8kuV0G31yz0qu2J3wkSI0tq3MR4+oZXFxDt8YkMevCUuQFZF4faYyTeFcdwRKlev0WexH1dWT22DUm+tkmcg2R1tv7TI/US6GTlXu/g9WGrLkHZjh1YQZ9AUNxtwL6O/CaKOuiR2nmmtYE=,iv:ojDH8+YWJQK3Rp6dHwj1T+/SVltnFDKKsOmYyeQpoMc=,tag:ieca6xM/sw7pjpu8czdv0A==,type:str] + lastmodified: "2025-12-11T22:11:51Z" + mac: ENC[AES256_GCM,data:7/xlEEZZLlElw3YufxHxRW47d5+w3q+mb+3qJrkTe38SSJDZ/MRyH3mR+T+Vm4jM4Iieh4wR2qz14EjIG6um4GSST84pvl2dj1dz8qd3/mrlWCQUxv/EUW5pSpDtd/HIPbfyMdmZApfspJ9CT40gOY2+91/KRzcHoGebe1ZMvV0=,iv:agzozhVd36roI81y8HYP26dt2DBxw77fJ7VafKyCw7Y=,tag:f6AZ0KaBO81rfDgG2R0Y/Q==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From 20423eb0cd237c82839f1c3b9d030e93dbe93e66 Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 11 Dec 2025 22:19:27 +0000 Subject: [PATCH 026/108] chore(secrets): set secret "zitadel/users" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 8a8b744..7709ea3 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -4,7 +4,7 @@ email: zitadel: masterKey: ENC[AES256_GCM,data:4MPvBo407qrS7NF4oUTf84tZoPkSRmiHdD7qpkYeHME=,iv:H2NIAN0xBUDqnyco9gA3zYAsKtSeA/JpqYrPhc1eqc0=,tag:6OFGDfsucG5gDerImgpuXA==,type:str] nix: {} - users: ENC[AES256_GCM,data:quxYk+XT5VZy+holUr3g5ycI34Z4BfSp2eKK4CZYBvl5ZES96Jf/oXCAWXhlEpXiKwsvKkAZNBdwLaqWrzRJJBzEi2UwZJfX1I0vDtWMz4VN5mKtzr+Vavty4visrleS46w2O/xg1PzOt/gv2CilyQmcBlpMbhLYVj4A9rbpiaIqXaYB/JMpa0sUyjjs/lxFDugv3pVEvmr7b0Gjqb1+A3TldEDxBT+P10jeomDoVJbMFyF09dpSQZrTlhyDHE742armspwvQyiKjmxmpkK1+L9iRgcBFiKCbkx4aZq5uvh3lNlVbsFqWhBbBRIOMjdgOm9OmQ7FGqqMSihsW6APMi9KTgpWUpk=,iv:mxtamxo8DWaxafC9AsgHKxcqNp5mLOEiPetoHZeA95c=,tag:ITNi8tMqkDxaONEKOYU/UQ==,type:str] + users: ENC[AES256_GCM,data:pMSK3Re/DZeMnFNCEgjTGWWMYYX5eLOoZwGg3oO7WQ0Sx7z7sLRPpqlGVw384G6uYjR19MpnVud6hHPkGY/FoTO0vsJ+a2anFpmLjLsPNehiQ57rnvnWJCeVJyTz0kqKt7vS1kGpdtjH5d98PerNzxR0FvTrjJhQCfHyP/S8/G6vD6cLmeBXaStpKJ6TM0UIPcWSTzrpV3O292xAFooWYv19hkM4C6IJtbej8zTmY8pEsHk5OY3w,iv:r3603xOtSE1CEdMR9epaWclbO3PXjMWpnJT7HEbF57o=,tag:HAPUlIuumY0IjL0P4Q1aFA==,type:str] forgejo: action_runner_token: ENC[AES256_GCM,data:yJ6OnRq5kinbuhvH06K5o3l86EafuBoojMwg/qhP+cgeH+BwPeE+Ng==,iv:IeXJahPxgLNIUFmkgp495tLVh8UyQBmJ2SnVEUhlhHs=,tag:XYQi613CxSp8AQeilJMrsg==,type:str] synapse: @@ -54,7 +54,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-12-11T22:11:51Z" - mac: ENC[AES256_GCM,data:7/xlEEZZLlElw3YufxHxRW47d5+w3q+mb+3qJrkTe38SSJDZ/MRyH3mR+T+Vm4jM4Iieh4wR2qz14EjIG6um4GSST84pvl2dj1dz8qd3/mrlWCQUxv/EUW5pSpDtd/HIPbfyMdmZApfspJ9CT40gOY2+91/KRzcHoGebe1ZMvV0=,iv:agzozhVd36roI81y8HYP26dt2DBxw77fJ7VafKyCw7Y=,tag:f6AZ0KaBO81rfDgG2R0Y/Q==,type:str] + lastmodified: "2025-12-11T22:19:26Z" + mac: ENC[AES256_GCM,data:J1RVA3s9qemyLGo4svCofqIA4XNYgDWDc3JRbfynGLtAocOQPtXOLKoEauplDWMQ8hFIGRznIzv5XkCH6hfxQhjNI0UCuR0WhFZtnQU59hS+Qg4AQKVukRdjY136RpNiBMCMNhiXs8NAbuVxrFramgFClFQgVO+b6+Q3w2JspNE=,iv:hPUnwDUg+Wbx/YDugY8TjFIJqUzJE75tv4vzc2NHdrQ=,tag:xwqb7I315SbAlK7MlT9uBA==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From 9824616c633ce14c35cb27691fa7042e3c9ea427 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Thu, 11 Dec 2025 23:20:36 +0100 Subject: [PATCH 027/108] feat: implement user management in just --- .jq/table.jq | 6 ++-- .just/users.just | 89 +++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 83 insertions(+), 12 deletions(-) diff --git a/.jq/table.jq b/.jq/table.jq index 83b98f2..5c58aef 100644 --- a/.jq/table.jq +++ b/.jq/table.jq @@ -26,7 +26,7 @@ def to_cells(sizes): to_cells(sizes; null); def to_line(left; joiner; right): [left, .[1], (.[1:] | map([joiner, .]) ), right] | flatten | join(""); -def to_table(data; header_callback; cell_callback): +def create(data; header_callback; cell_callback): (data[0] | to_entries | map(.key)) as $keys | ([$keys]) as $header | (data | map(to_entries | map(.value))) as $rows @@ -55,5 +55,5 @@ def to_table(data; header_callback; cell_callback): | join("\n") ); -def to_table(data; header_callback): to_table(data; header_callback; null); -def to_table(data): to_table(data; _::style(_::BOLD); null); +def create(data; header_callback): create(data; header_callback; null); +def create(data): create(data; _::style(_::BOLD); null); diff --git a/.just/users.just b/.just/users.just index cecd74b..486ac67 100644 --- a/.just/users.just +++ b/.just/users.just @@ -6,17 +6,16 @@ _default: [script] list: - cd .. && just vars get ulmo zitadel/users \ - | jq fromjson \ - | jq -r -C ' - include ".jq/table"; - include ".jq/format"; + cd .. && just vars get ulmo zitadel/users | jq -r -C ' + import ".jq/table" as table; + import ".jq/format" as f; - to_entries + fromjson + | to_entries | sort_by(.key) | map( - (.key|to_title) + ":\n" - + to_table( + (.key|f::to_title) + ":\n" + + table::create( .value | to_entries | sort_by(.key) @@ -24,4 +23,76 @@ list: ) ) | join("\n\nβ”„β”„β”„\n\n") - ' + '; + +[script] +add: + exec 5>&1 + + pad () { [ "$#" -gt 1 ] && [ -n "$2" ] && printf "%$2.${2#-}s" "$1"; } + + input() { + local label=$1 + local value=$2 + + local res=$(gum input --header "$label" --value "$value") + echo -e "\e[2m$(pad "$label" -11)\e[0m$res" >&5 + echo $res + } + + data=`cd .. && just vars get ulmo zitadel/users | jq 'fromjson'` + + # Gather inputs + org=` + jq -r 'to_entries | map(.key)[]' <<< "$data" \ + | gum choose --header 'Which organisation to save to?' --select-if-one + ` + username=`input 'user name' 'new-user'` + email=`input 'email' 'new.user@example.com'` + first_name=`input 'first name' 'John'` + last_name=`input 'last name' 'Doe'` + + user_exists=`jq --arg 'org' "$org" --arg 'username' "$username" '.[$org][$username]? | . != null' <<< "$data"` + + if [ "$user_exists" == "true" ]; then + gum confirm 'User already exists, overwrite it?' --padding="1 1" || exit 0 + fi + + next=` + jq \ + --arg 'org' "$org" \ + --arg 'username' "$username" \ + --arg 'email' "$email" \ + --arg 'first_name' "$first_name" \ + --arg 'last_name' "$last_name" \ + --compact-output \ + '.[$org] += { $username: { email: $email, firstName: $first_name, lastName: $last_name } }' \ + <<< $data + ` + + gum spin --title "saving..." -- echo "$(cd .. && just vars set ulmo 'zitadel/users' "$next")" + +[script] +remove: + data=`cd .. && just vars get ulmo zitadel/users | jq fromjson` + + # Gather inputs + org=` + jq -r 'to_entries | map(.key)[]' <<< "$data" \ + | gum choose --header 'Which organisation?' --select-if-one + ` + user=` + jq -r --arg org "$org" '.[$org] | to_entries | map(.key)[]' <<< "$data" \ + | gum choose --header 'Which user?' --select-if-one + ` + + next=` + jq \ + --arg 'org' "$org" \ + --arg 'user' "$user" \ + --compact-output \ + 'del(.[$org][$user])' \ + <<< $data + ` + + gum spin --title "saving..." -- echo "$(cd .. && just vars set ulmo 'zitadel/users' "$next")" From a361274b27f7db57a2f461a6ec206bcb3405728f Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 11 Dec 2025 22:21:37 +0000 Subject: [PATCH 028/108] chore: update dependencies --- flake.lock | 68 +++++++++++++++++++++++++++--------------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/flake.lock b/flake.lock index cd4b600..f4f2381 100644 --- a/flake.lock +++ b/flake.lock @@ -84,11 +84,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1765346666, - "narHash": "sha256-UR8bVZF12rA7yI3jdqvlTA50NUXf3F8H6GZvLYiDqYM=", - "rev": "7c9a2e4fb9d90f213f3bf3782ee460e669231bca", + "lastModified": 1765483630, + "narHash": "sha256-4nLng3hXTHuJF1xeMXWVyK26r0O407YG7aEfkWVD3Jg=", + "rev": "b500c2e4c8f50961e976b7a78991d2fd4f96c423", "type": "tarball", - "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/7c9a2e4fb9d90f213f3bf3782ee460e669231bca.tar.gz" + "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/b500c2e4c8f50961e976b7a78991d2fd4f96c423.tar.gz" }, "original": { "type": "tarball", @@ -170,11 +170,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1765252472, - "narHash": "sha256-byMt/uMi7DJ8tRniFopDFZMO3leSjGp6GS4zWOFT+uQ=", + "lastModified": 1765435813, + "narHash": "sha256-C6tT7K1Lx6VsYw1BY5S3OavtapUvEnDQtmQB5DSgbCc=", "owner": "nix-community", "repo": "fenix", - "rev": "8456b985f6652e3eef0632ee9992b439735c5544", + "rev": "6399553b7a300c77e7f07342904eb696a5b6bf9d", "type": "github" }, "original": { @@ -190,11 +190,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1765370621, - "narHash": "sha256-3gAVH9nYc2E82tIXKFv2lMe4JohglxJtPgs0ZmXkx9c=", + "lastModified": 1765448647, + "narHash": "sha256-y29oz4/jfs7TEGR1+pKlcQn5pBsTZGM8TOhVDJEAtXg=", "owner": "nix-community", "repo": "flake-firefox-nightly", - "rev": "ea98c8041dad75efc80ec036643a32b12467c8b7", + "rev": "63947e060742f3b023c87e225c3f327befbbd6a3", "type": "github" }, "original": { @@ -594,11 +594,11 @@ ] }, "locked": { - "lastModified": 1765337252, - "narHash": "sha256-HuWQp8fM25fyWflbuunQkQI62Hg0ecJxWD52FAgmxqY=", + "lastModified": 1765480374, + "narHash": "sha256-HlbvQAqLx7WqZFFQZ8nu5UUJAVlXiV/kqKbyueA8srw=", "owner": "nix-community", "repo": "home-manager", - "rev": "13cc1efd78b943b98c08d74c9060a5b59bf86921", + "rev": "39cb677ed9e908e90478aa9fe5f3383dfc1a63f3", "type": "github" }, "original": { @@ -810,11 +810,11 @@ }, "nixos-facter-modules": { "locked": { - "lastModified": 1764252389, - "narHash": "sha256-3bbuneTKZBkYXlm0bE36kUjiDsasoIC1GWBw/UEJ9T4=", + "lastModified": 1765442039, + "narHash": "sha256-k3lYQ+A1F7aTz8HnlU++bd9t/x/NP2A4v9+x6opcVg0=", "owner": "nix-community", "repo": "nixos-facter-modules", - "rev": "5ea68886d95218646d11d3551a476d458df00778", + "rev": "9dd775ee92de63f14edd021d59416e18ac2c00f1", "type": "github" }, "original": { @@ -852,11 +852,11 @@ ] }, "locked": { - "lastModified": 1765376994, - "narHash": "sha256-dsgdFdj8+qh81XPB/9SlwvuhJMHPjqsf7Zk0AnsdVpY=", + "lastModified": 1765483419, + "narHash": "sha256-w6wznH1lBzlSH3+pWDkE+L6xA0F02drFAzu2E7PD/Jo=", "owner": "nix-community", "repo": "nixos-wsl", - "rev": "30f6a14293df4938c35173a73efdeba450653d0a", + "rev": "0c040f28b44b18e0d4240e027096078e34dbb029", "type": "github" }, "original": { @@ -914,11 +914,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1765357816, - "narHash": "sha256-Uh7y3tL9SUzMjM8eO9CMqf30pPpa1i+P3asBijc32lY=", + "lastModified": 1765403794, + "narHash": "sha256-bOk4vZjzk419pIkmMWrr8PTg0fK2Oph/owZUAPHWwIE=", "owner": "nixos", "repo": "nixpkgs", - "rev": "004943ed3cf9de5805a0da377599d1bfdd47a98a", + "rev": "6f313d8e9be4d7db523962ecc3ce97c951bacb1f", "type": "github" }, "original": { @@ -946,11 +946,11 @@ }, "nixpkgs_4": { "locked": { - "lastModified": 1765380834, - "narHash": "sha256-MUMk4DZ0V+gU7yee7DdiPwieRclS2uMNvLQGLWwew4M=", + "lastModified": 1765491281, + "narHash": "sha256-adRTsIAzAiMUP40dPHhcAq69+iRcSV93XJdg8YO7lYw=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "bf83174d5ab54f384b1ec5068b3280241dbb849f", + "rev": "d21f5e3178bdfce2e894e0bd9b6535ac6593a734", "type": "github" }, "original": { @@ -1139,11 +1139,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1765120009, - "narHash": "sha256-nG76b87rkaDzibWbnB5bYDm6a52b78A+fpm+03pqYIw=", + "lastModified": 1765400135, + "narHash": "sha256-D3+4hfNwUhG0fdCpDhOASLwEQ1jKuHi4mV72up4kLQM=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "5e3e9c4e61bba8a5e72134b9ffefbef8f531d008", + "rev": "fface27171988b3d605ef45cf986c25533116f7e", "type": "github" }, "original": { @@ -1254,11 +1254,11 @@ "tinted-zed": "tinted-zed" }, "locked": { - "lastModified": 1765377959, - "narHash": "sha256-MsvpqrovI+iveyVam6sIPlSsUVVcmmhTxpD9w3OOsvw=", + "lastModified": 1765474444, + "narHash": "sha256-sDG+c73xEnIw1pFNRWffKDnTWiTuyZiEP+Iub0D3mWA=", "owner": "nix-community", "repo": "stylix", - "rev": "54fcd2f342c6417548cc56f53e401224dcade639", + "rev": "dd14de4432a94e93e10d0159f1d411487e435e1e", "type": "github" }, "original": { @@ -1519,11 +1519,11 @@ ] }, "locked": { - "lastModified": 1765344150, - "narHash": "sha256-RoGBKQglbF19aINeV8F7DHCXxF7FrMRLgL2yjl9vOiQ=", + "lastModified": 1765491669, + "narHash": "sha256-LjMIEOyIT5AMvbz/RYRcZPTJ7FB6vnEmeaid9vkIp0k=", "owner": "0xc000022070", "repo": "zen-browser-flake", - "rev": "1adab25828578301037855c59849e9bbecf8948b", + "rev": "85feeba579822e7e30cccf549d805f24b86d7235", "type": "github" }, "original": { From 1ee8fa34077bf5fb75018df350afd84eb12e2fa8 Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 11 Dec 2025 22:47:02 +0000 Subject: [PATCH 029/108] chore(secrets): set secret "zitadel/users" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 7709ea3..4c9cc8d 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -4,7 +4,7 @@ email: zitadel: masterKey: ENC[AES256_GCM,data:4MPvBo407qrS7NF4oUTf84tZoPkSRmiHdD7qpkYeHME=,iv:H2NIAN0xBUDqnyco9gA3zYAsKtSeA/JpqYrPhc1eqc0=,tag:6OFGDfsucG5gDerImgpuXA==,type:str] nix: {} - users: ENC[AES256_GCM,data:pMSK3Re/DZeMnFNCEgjTGWWMYYX5eLOoZwGg3oO7WQ0Sx7z7sLRPpqlGVw384G6uYjR19MpnVud6hHPkGY/FoTO0vsJ+a2anFpmLjLsPNehiQ57rnvnWJCeVJyTz0kqKt7vS1kGpdtjH5d98PerNzxR0FvTrjJhQCfHyP/S8/G6vD6cLmeBXaStpKJ6TM0UIPcWSTzrpV3O292xAFooWYv19hkM4C6IJtbej8zTmY8pEsHk5OY3w,iv:r3603xOtSE1CEdMR9epaWclbO3PXjMWpnJT7HEbF57o=,tag:HAPUlIuumY0IjL0P4Q1aFA==,type:str] + users: ENC[AES256_GCM,data:yxdJ2PmOJXXCF2NaD1QWLSuwF9AhdIBhLiZDm4GhcTb4sA3zGTyJBw5saH6P5QAwk9ngbOgn8RH0vgeYEJ0z8VzUoCaLWK5xaqLggYgd75ewNQu7Jkh6V/oSHeVfv+6NCRoq4PckHvhBHwQQ4uToaCghUbjX6VJlFSKwSAy6laG30UMIa2Q4hTQHqgVcbjpQUJSu6/ajDz3Ap0MqhCTSOPWKZ9vWZpvRnFhLhsJrTNl0w6zlCuZcy8xqn/zZo4OEuexHr29yFFohbiD9L9CLd0N6NYDMX7eHRjjdB6Ysxfkic9JSWysma/7OwPzg/KK+pQDkNi7ciR+/cT9Gqn73IFpXPvuooe+7wxe4INfGq3iAoRIYSz8=,iv:opqL2iB3sqT+/a03tTzWphFGnwrEwdKybnj/3BNzL3U=,tag:2+CMLgKdsWpPsYrkKAP5hg==,type:str] forgejo: action_runner_token: ENC[AES256_GCM,data:yJ6OnRq5kinbuhvH06K5o3l86EafuBoojMwg/qhP+cgeH+BwPeE+Ng==,iv:IeXJahPxgLNIUFmkgp495tLVh8UyQBmJ2SnVEUhlhHs=,tag:XYQi613CxSp8AQeilJMrsg==,type:str] synapse: @@ -54,7 +54,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-12-11T22:19:26Z" - mac: ENC[AES256_GCM,data:J1RVA3s9qemyLGo4svCofqIA4XNYgDWDc3JRbfynGLtAocOQPtXOLKoEauplDWMQ8hFIGRznIzv5XkCH6hfxQhjNI0UCuR0WhFZtnQU59hS+Qg4AQKVukRdjY136RpNiBMCMNhiXs8NAbuVxrFramgFClFQgVO+b6+Q3w2JspNE=,iv:hPUnwDUg+Wbx/YDugY8TjFIJqUzJE75tv4vzc2NHdrQ=,tag:xwqb7I315SbAlK7MlT9uBA==,type:str] + lastmodified: "2025-12-11T22:47:01Z" + mac: ENC[AES256_GCM,data:7d8MOO5luFUGI2DhHUQRoBG4097/KnKvmj32vbdgoPFQWuLmePAdhJJ+n2T3Si89eLNoZVpwd2gvunSLiKG+rTyvVxf3jx1bOuGOhL2VlbbnLHiJPhX6Wi3NhRRUDqWl3oNcq/G9f6Y+fJ5R+Cofkov+aTPhIAM+Ceq/7Bf+J/M=,iv:zpf8r+W2QjUAmULnjzEQdESF4cuNWa3aMIVo6FPe/YI=,tag:z5YFIqlS1URIRsnqjFLquw==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From b2a0a2a26d90df96d292bdb29a63898c30c73507 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Thu, 18 Dec 2025 15:11:04 +0100 Subject: [PATCH 030/108] fix: only enable media services when needed --- modules/nixos/services/media/servarr/default.nix | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/nixos/services/media/servarr/default.nix b/modules/nixos/services/media/servarr/default.nix index bb90352..057b810 100644 --- a/modules/nixos/services/media/servarr/default.nix +++ b/modules/nixos/services/media/servarr/default.nix @@ -11,6 +11,7 @@ inherit (lib) mkIf mkEnableOption mkOption types; cfg = config.${namespace}.services.media.servarr; + anyEnabled = cfg |> lib.attrNames |> lib.length |> (l: l > 0); in { options.${namespace}.services.media = { servarr = mkOption { @@ -33,7 +34,7 @@ in { }; }; - config = { + config = mkIf anyEnabled { services = cfg |> lib.mapAttrsToList (service: { @@ -269,6 +270,11 @@ in { }; groups.${service} = {}; })) + |> lib.concat [ + { + groups.media = {}; + } + ] |> lib.mkMerge; sops = From c935a034c4661a6a88c253d80aac957903d17b4a Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Thu, 18 Dec 2025 15:58:01 +0100 Subject: [PATCH 031/108] AAAARGHHH --- systems/x86_64-linux/manwe/default.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/systems/x86_64-linux/manwe/default.nix b/systems/x86_64-linux/manwe/default.nix index c2d9978..3839cc6 100644 --- a/systems/x86_64-linux/manwe/default.nix +++ b/systems/x86_64-linux/manwe/default.nix @@ -1,5 +1,4 @@ -{ ... }: -{ +{...}: { imports = [ ./disks.nix ./hardware.nix @@ -7,6 +6,8 @@ system.activationScripts.remove-gtkrc.text = "rm -f /home/chris/.gtkrc-2.0"; + services.logrotate.checkConfig = false; + sneeuwvlok = { hardware.has = { gpu.amd = true; @@ -30,7 +31,6 @@ }; }; - services.displayManager.autoLogin = { enable = true; user = "chris"; From 821767765fea85f7b529a93db27bce820796fc99 Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 18 Dec 2025 15:01:33 +0000 Subject: [PATCH 032/108] chore: update dependencies --- flake.lock | 213 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 124 insertions(+), 89 deletions(-) diff --git a/flake.lock b/flake.lock index f4f2381..004e726 100644 --- a/flake.lock +++ b/flake.lock @@ -21,17 +21,17 @@ "base16-fish": { "flake": false, "locked": { - "lastModified": 1754405784, - "narHash": "sha256-l9xHIy+85FN+bEo6yquq2IjD1rSg9fjfjpyGP1W8YXo=", + "lastModified": 1765809053, + "narHash": "sha256-XCUQLoLfBJ8saWms2HCIj4NEN+xNsWBlU1NrEPcQG4s=", "owner": "tomyun", "repo": "base16-fish", - "rev": "23ae20a0093dca0d7b39d76ba2401af0ccf9c561", + "rev": "86cbea4dca62e08fb7fd83a70e96472f92574782", "type": "github" }, "original": { "owner": "tomyun", "repo": "base16-fish", - "rev": "23ae20a0093dca0d7b39d76ba2401af0ccf9c561", + "rev": "86cbea4dca62e08fb7fd83a70e96472f92574782", "type": "github" } }, @@ -84,11 +84,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1765483630, - "narHash": "sha256-4nLng3hXTHuJF1xeMXWVyK26r0O407YG7aEfkWVD3Jg=", - "rev": "b500c2e4c8f50961e976b7a78991d2fd4f96c423", + "lastModified": 1766058975, + "narHash": "sha256-HBnRRq9wLq7UfJxMM55wR10lZFK1F0lNyRgUwwOby6s=", + "rev": "9032d11a0e31641808ef1427150aac0f40e2e0b9", "type": "tarball", - "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/b500c2e4c8f50961e976b7a78991d2fd4f96c423.tar.gz" + "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/9032d11a0e31641808ef1427150aac0f40e2e0b9.tar.gz" }, "original": { "type": "tarball", @@ -111,11 +111,11 @@ ] }, "locked": { - "lastModified": 1765163284, - "narHash": "sha256-tCrc6IyhXrMTTeF5lZHlwbfMBvDUr0OM5Uz+kToJ+ow=", - "rev": "986035f01ba7339c6c9d80f37aec9c5f93dfa47f", + "lastModified": 1765768061, + "narHash": "sha256-RZ/ocDUJ3WPr2KcDc2MB6Fu+ZPqzwsMKQ16XxqrPi+o=", + "rev": "53351f9953ecf9dbe18795b4784abe53b14e6eee", "type": "tarball", - "url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/986035f01ba7339c6c9d80f37aec9c5f93dfa47f.tar.gz" + "url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/53351f9953ecf9dbe18795b4784abe53b14e6eee.tar.gz" }, "original": { "type": "tarball", @@ -130,11 +130,11 @@ ] }, "locked": { - "lastModified": 1765326679, - "narHash": "sha256-fTLX9kDwLr9Y0rH/nG+h1XG5UU+jBcy0PFYn5eneRX8=", + "lastModified": 1765794845, + "narHash": "sha256-YD5QWlGnusNbZCqR3pxG8tRxx9yUXayLZfAJRWspq2s=", "owner": "nix-community", "repo": "disko", - "rev": "d64e5cdca35b5fad7c504f615357a7afe6d9c49e", + "rev": "7194cfe5b7a3660726b0fe7296070eaef601cae9", "type": "github" }, "original": { @@ -149,11 +149,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1764775116, - "narHash": "sha256-S4fY3fytcqXBuOSbQjEVke2eqK9/e/6Jy3jp0JGM2X4=", + "lastModified": 1765983453, + "narHash": "sha256-cH32B1gu+/AQA8arQScPStZhmT73jDnrzSejuNR//F0=", "owner": "emmanuelrosa", "repo": "erosanix", - "rev": "172661ccc78b1529a294eee5e99ca1616c934f37", + "rev": "b742cf8ae93417efb2c1bbcc70146a664126d8be", "type": "github" }, "original": { @@ -190,11 +190,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1765448647, - "narHash": "sha256-y29oz4/jfs7TEGR1+pKlcQn5pBsTZGM8TOhVDJEAtXg=", + "lastModified": 1766067704, + "narHash": "sha256-k8JGGuFfml1qsMI5AW1f1+2+6S3YAHkw+AHXACkZ+l8=", "owner": "nix-community", "repo": "flake-firefox-nightly", - "rev": "63947e060742f3b023c87e225c3f327befbbd6a3", + "rev": "59d2f3f13afbf4fc88431316c0eef124f1e6e822", "type": "github" }, "original": { @@ -306,11 +306,11 @@ ] }, "locked": { - "lastModified": 1763759067, - "narHash": "sha256-LlLt2Jo/gMNYAwOgdRQBrsRoOz7BPRkzvNaI/fzXi2Q=", + "lastModified": 1765835352, + "narHash": "sha256-XswHlK/Qtjasvhd1nOa1e8MgZ8GS//jBoTqWtrS1Giw=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "2cccadc7357c0ba201788ae99c4dfa90728ef5e0", + "rev": "a34fae9c08a15ad73f295041fec82323541400a9", "type": "github" }, "original": { @@ -574,11 +574,11 @@ "rust-overlay": "rust-overlay" }, "locked": { - "lastModified": 1765227577, - "narHash": "sha256-2YyCvI3aGFkFfT6JKmaer8YyhwAk6lJwO6vCikqJwa8=", + "lastModified": 1765998060, + "narHash": "sha256-RGluSceSmt/1wZVwnkrLGc+oImO94o723fNPTEJm25w=", "owner": "himmelblau-idm", "repo": "himmelblau", - "rev": "70b63803f6429dafa20be0035548072092e0e512", + "rev": "3ba7e3c89968d1a8f70c14550df58bdd997b998d", "type": "github" }, "original": { @@ -594,11 +594,11 @@ ] }, "locked": { - "lastModified": 1765480374, - "narHash": "sha256-HlbvQAqLx7WqZFFQZ8nu5UUJAVlXiV/kqKbyueA8srw=", + "lastModified": 1765980955, + "narHash": "sha256-rB45jv4uwC90vM9UZ70plfvY/2Kdygs+zlQ07dGQFk4=", "owner": "nix-community", "repo": "home-manager", - "rev": "39cb677ed9e908e90478aa9fe5f3383dfc1a63f3", + "rev": "89c9508bbe9b40d36b3dc206c2483ef176f15173", "type": "github" }, "original": { @@ -615,11 +615,11 @@ ] }, "locked": { - "lastModified": 1762964643, - "narHash": "sha256-RYHN8O/Aja59XDji6WSJZPkJpYVUfpSkyH+PEupBJqM=", + "lastModified": 1765682243, + "narHash": "sha256-yeCxFV/905Wr91yKt5zrVvK6O2CVXWRMSrxqlAZnLp0=", "owner": "nix-community", "repo": "home-manager", - "rev": "827f2a23373a774a8805f84ca5344654c31f354b", + "rev": "58bf3ecb2d0bba7bdf363fc8a6c4d49b4d509d03", "type": "github" }, "original": { @@ -636,11 +636,11 @@ ] }, "locked": { - "lastModified": 1765365489, - "narHash": "sha256-L0uvs+o8P5JzEcTPe2WPA48+0ZiO6+8nlfh7XSjQql4=", + "lastModified": 1766067735, + "narHash": "sha256-cRC/rOYRtZNzc5y9nTccozyo/mkI4/1eFE33Aqgs+SQ=", "owner": "Jovian-Experiments", "repo": "Jovian-NixOS", - "rev": "ddf5db234397043a8af5c38433b5ae933d660f27", + "rev": "34a16089be30f77ac9444907ec97c02b4b711896", "type": "github" }, "original": { @@ -655,11 +655,11 @@ "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1765111385, - "narHash": "sha256-Gn8IIq9FGLvQSDK2bXKzsqqkgKExTExLkYfH7n8Nnpk=", + "lastModified": 1765716362, + "narHash": "sha256-ZjICTdeEXwhioCJyYeEIrP5UlW6QZvP8rAAquNy+vso=", "owner": "nix-community", "repo": "lib-aggregate", - "rev": "e562ca084a8b3490337d446f1e0d6afadb509d1e", + "rev": "b70d4898e9a4d2ad2d3ff870b9674f371b119fef", "type": "github" }, "original": { @@ -702,6 +702,24 @@ "type": "github" } }, + "ndg": { + "inputs": { + "nixpkgs": "nixpkgs_8" + }, + "locked": { + "lastModified": 1765720983, + "narHash": "sha256-tWtukpABmux6EC/FuCJEgA1kmRjcRPtED44N+GGPq+4=", + "owner": "feel-co", + "repo": "ndg", + "rev": "f399ace8bb8e1f705dd8942b24d207aa4d75c936", + "type": "github" + }, + "original": { + "owner": "feel-co", + "repo": "ndg", + "type": "github" + } + }, "nix-darwin": { "inputs": { "nixpkgs": [ @@ -752,11 +770,11 @@ "nixpkgs": "nixpkgs_6" }, "locked": { - "lastModified": 1765332486, - "narHash": "sha256-nVTejyI8w3ePrX4tW3lBLLg3DheqhRuxtiRefT+ynrk=", + "lastModified": 1766023574, + "narHash": "sha256-vx7KhTqR/UBnBUXAei3DKXJ4Nq3p7yLw+kZ03/inm8I=", "owner": "Infinidoge", "repo": "nix-minecraft", - "rev": "a3bdc14045dc7e5fb7a94ab11064766f472279eb", + "rev": "5e0cae13ca72d3e4ef0f101b01725e25441c4ebd", "type": "github" }, "original": { @@ -852,11 +870,11 @@ ] }, "locked": { - "lastModified": 1765483419, - "narHash": "sha256-w6wznH1lBzlSH3+pWDkE+L6xA0F02drFAzu2E7PD/Jo=", + "lastModified": 1765841014, + "narHash": "sha256-55V0AJ36V5Egh4kMhWtDh117eE3GOjwq5LhwxDn9eHg=", "owner": "nix-community", "repo": "nixos-wsl", - "rev": "0c040f28b44b18e0d4240e027096078e34dbb029", + "rev": "be4af8042e7a61fa12fda58fe9a3b3babdefe17b", "type": "github" }, "original": { @@ -883,11 +901,11 @@ }, "nixpkgs-lib": { "locked": { - "lastModified": 1765070080, - "narHash": "sha256-5D1Mcm2dQ1aPzQ0sbXluHVUHququ8A7PKJd7M3eI9+E=", + "lastModified": 1765674936, + "narHash": "sha256-k00uTP4JNfmejrCLJOwdObYC9jHRrr/5M/a/8L2EIdo=", "owner": "nix-community", "repo": "nixpkgs.lib", - "rev": "e0cad9791b0c168931ae562977703b72d9360836", + "rev": "2075416fcb47225d9b68ac469a5c4801a9c4dd85", "type": "github" }, "original": { @@ -897,6 +915,22 @@ } }, "nixpkgs_10": { + "locked": { + "lastModified": 1765457389, + "narHash": "sha256-ddhDtNYvleZeYF7g7TRFSmuQuZh7HCgqstg5YBGwo5s=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "f997fa0f94fb1ce55bccb97f60d41412ae8fde4c", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_11": { "locked": { "lastModified": 1764517877, "narHash": "sha256-pp3uT4hHijIC8JUK5MEqeAWmParJrgBVzHLNfJDZxg4=", @@ -914,11 +948,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1765403794, - "narHash": "sha256-bOk4vZjzk419pIkmMWrr8PTg0fK2Oph/owZUAPHWwIE=", + "lastModified": 1766035038, + "narHash": "sha256-tJ8RIG8JiMV9NGYWegYw1bEO/Ja09mH1y7RhuviEN78=", "owner": "nixos", "repo": "nixpkgs", - "rev": "6f313d8e9be4d7db523962ecc3ce97c951bacb1f", + "rev": "da5cc354cf31d8514d763c39f5a685da3fe1eb3e", "type": "github" }, "original": { @@ -946,11 +980,11 @@ }, "nixpkgs_4": { "locked": { - "lastModified": 1765491281, - "narHash": "sha256-adRTsIAzAiMUP40dPHhcAq69+iRcSV93XJdg8YO7lYw=", + "lastModified": 1766068349, + "narHash": "sha256-pb4NHaLbyEo5C+h1XfQAkdI/ceKrKO1YvUEZGLrdIMo=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "d21f5e3178bdfce2e894e0bd9b6535ac6593a734", + "rev": "fe17732c30a65edbd3f101db6aa6f76a38e21ac2", "type": "github" }, "original": { @@ -994,11 +1028,11 @@ }, "nixpkgs_7": { "locked": { - "lastModified": 1765186076, - "narHash": "sha256-hM20uyap1a0M9d344I692r+ik4gTMyj60cQWO+hAYP8=", + "lastModified": 1765779637, + "narHash": "sha256-KJ2wa/BLSrTqDjbfyNx70ov/HdgNBCBBSQP3BIzKnv4=", "owner": "nixos", "repo": "nixpkgs", - "rev": "addf7cf5f383a3101ecfba091b98d0a1263dc9b8", + "rev": "1306659b587dc277866c7b69eb97e5f07864d8c4", "type": "github" }, "original": { @@ -1010,31 +1044,31 @@ }, "nixpkgs_8": { "locked": { - "lastModified": 1761880412, - "narHash": "sha256-QoJjGd4NstnyOG4mm4KXF+weBzA2AH/7gn1Pmpfcb0A=", - "owner": "nixos", + "lastModified": 1764242076, + "narHash": "sha256-sKoIWfnijJ0+9e4wRvIgm/HgE27bzwQxcEmo2J/gNpI=", + "owner": "NixOS", "repo": "nixpkgs", - "rev": "a7fc11be66bdfb5cdde611ee5ce381c183da8386", + "rev": "2fad6eac6077f03fe109c4d4eb171cf96791faa4", "type": "github" }, "original": { - "owner": "nixos", - "ref": "nixpkgs-unstable", + "owner": "NixOS", + "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } }, "nixpkgs_9": { "locked": { - "lastModified": 1764947035, - "narHash": "sha256-EYHSjVM4Ox4lvCXUMiKKs2vETUSL5mx+J2FfutM7T9w=", - "owner": "NixOS", + "lastModified": 1764081664, + "narHash": "sha256-sUoHmPr/EwXzRMpv1u/kH+dXuvJEyyF2Q7muE+t0EU4=", + "owner": "nixos", "repo": "nixpkgs", - "rev": "a672be65651c80d3f592a89b3945466584a22069", + "rev": "dc205f7b4fdb04c8b7877b43edb7b73be7730081", "type": "github" }, "original": { - "owner": "NixOS", + "owner": "nixos", "ref": "nixpkgs-unstable", "repo": "nixpkgs", "type": "github" @@ -1070,15 +1104,16 @@ "flake-compat": "flake-compat_4", "flake-parts": "flake-parts_3", "mnw": "mnw", - "nixpkgs": "nixpkgs_8", + "ndg": "ndg", + "nixpkgs": "nixpkgs_9", "systems": "systems_5" }, "locked": { - "lastModified": 1765119282, - "narHash": "sha256-iI0fuBBYJMnOprGD2L+rum2P8lHMcZ5n35hzdlpwayI=", + "lastModified": 1765894398, + "narHash": "sha256-vZmKngMAaZ38mUjWeXavagrCE5f4h4s9yIShf2q75I4=", "owner": "notashelf", "repo": "nvf", - "rev": "26c4a7e3c33e739d474ddaf52aa4c5f3d11922ba", + "rev": "cd81bbb904571b538397d72a29e4c84b98480ee1", "type": "github" }, "original": { @@ -1204,11 +1239,11 @@ ] }, "locked": { - "lastModified": 1765231718, - "narHash": "sha256-qdBzo6puTgG4G2RHG0PkADg22ZnQo1JmSVFRxrD4QM4=", + "lastModified": 1765836173, + "narHash": "sha256-hWRYfdH2ONI7HXbqZqW8Q1y9IRbnXWvtvt/ONZovSNY=", "owner": "Mic92", "repo": "sops-nix", - "rev": "7fd1416aba1865eddcdec5bb11339b7222c2363e", + "rev": "443a7f2e7e118c4fc63b7fae05ab3080dd0e5c63", "type": "github" }, "original": { @@ -1219,14 +1254,14 @@ }, "sops-nix_2": { "inputs": { - "nixpkgs": "nixpkgs_9" + "nixpkgs": "nixpkgs_10" }, "locked": { - "lastModified": 1765231718, - "narHash": "sha256-qdBzo6puTgG4G2RHG0PkADg22ZnQo1JmSVFRxrD4QM4=", + "lastModified": 1765836173, + "narHash": "sha256-hWRYfdH2ONI7HXbqZqW8Q1y9IRbnXWvtvt/ONZovSNY=", "owner": "Mic92", "repo": "sops-nix", - "rev": "7fd1416aba1865eddcdec5bb11339b7222c2363e", + "rev": "443a7f2e7e118c4fc63b7fae05ab3080dd0e5c63", "type": "github" }, "original": { @@ -1244,7 +1279,7 @@ "firefox-gnome-theme": "firefox-gnome-theme", "flake-parts": "flake-parts_4", "gnome-shell": "gnome-shell", - "nixpkgs": "nixpkgs_10", + "nixpkgs": "nixpkgs_11", "nur": "nur", "systems": "systems_7", "tinted-foot": "tinted-foot", @@ -1254,11 +1289,11 @@ "tinted-zed": "tinted-zed" }, "locked": { - "lastModified": 1765474444, - "narHash": "sha256-sDG+c73xEnIw1pFNRWffKDnTWiTuyZiEP+Iub0D3mWA=", + "lastModified": 1765897595, + "narHash": "sha256-NgTRxiEC5y96zrhdBygnY+mSzk5FWMML39PcRGVJmxg=", "owner": "nix-community", "repo": "stylix", - "rev": "dd14de4432a94e93e10d0159f1d411487e435e1e", + "rev": "e6829552d4bb659ebab00f08c61d8c62754763f3", "type": "github" }, "original": { @@ -1498,11 +1533,11 @@ ] }, "locked": { - "lastModified": 1762938485, - "narHash": "sha256-AlEObg0syDl+Spi4LsZIBrjw+snSVU4T8MOeuZJUJjM=", + "lastModified": 1766000401, + "narHash": "sha256-+cqN4PJz9y0JQXfAK5J1drd0U05D5fcAGhzhfVrDlsI=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "5b4ee75aeefd1e2d5a1cc43cf6ba65eba75e83e4", + "rev": "42d96e75aa56a3f70cab7e7dc4a32868db28e8fd", "type": "github" }, "original": { @@ -1519,11 +1554,11 @@ ] }, "locked": { - "lastModified": 1765491669, - "narHash": "sha256-LjMIEOyIT5AMvbz/RYRcZPTJ7FB6vnEmeaid9vkIp0k=", + "lastModified": 1766032508, + "narHash": "sha256-7MHR94mOoa5/s4NBrpsXWaNNzrZyRC0OwRwEobp1wzI=", "owner": "0xc000022070", "repo": "zen-browser-flake", - "rev": "85feeba579822e7e30cccf549d805f24b86d7235", + "rev": "a7f58a9e3481804915d75a9c86527bca6d9dafb3", "type": "github" }, "original": { From 73929e0cf9e966bfed10d842c8900e4c51ddcf1e Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Thu, 18 Dec 2025 16:22:43 +0100 Subject: [PATCH 033/108] I'm so tired of all this crap... --- modules/nixos/desktop/cosmic/default.nix | 32 ++++++++++++++++++++++++ modules/nixos/desktop/default.nix | 18 +++++++------ systems/x86_64-linux/manwe/default.nix | 2 +- 3 files changed, 44 insertions(+), 8 deletions(-) create mode 100644 modules/nixos/desktop/cosmic/default.nix diff --git a/modules/nixos/desktop/cosmic/default.nix b/modules/nixos/desktop/cosmic/default.nix new file mode 100644 index 0000000..ff4d047 --- /dev/null +++ b/modules/nixos/desktop/cosmic/default.nix @@ -0,0 +1,32 @@ +{ + lib, + config, + namespace, + inputs, + ... +}: let + inherit (lib) mkIf mkEnableOption mkForce; + + cfg = config.${namespace}.desktop.cosmic; +in { + options.${namespace}.desktop.cosmic = { + enable = + mkEnableOption "Enable Cosmic desktop" + // { + default = config.${namespace}.desktop.use == "cosmic"; + }; + }; + + config = mkIf cfg.enable { + services = { + displayManager = { + cosmic-greeter.enable = true; + autoLogin = { + enable = true; + user = "chris"; + }; + }; + desktopManager.cosmic.enable = true; + }; + }; +} diff --git a/modules/nixos/desktop/default.nix b/modules/nixos/desktop/default.nix index 9fd9192..13ef881 100644 --- a/modules/nixos/desktop/default.nix +++ b/modules/nixos/desktop/default.nix @@ -1,18 +1,22 @@ -{ lib, config, namespace, inputs, ... }: -let +{ + lib, + config, + namespace, + inputs, + ... +}: let inherit (lib) mkIf mkOption mkEnableOption mkMerge; inherit (lib.types) nullOr enum; cfg = config.${namespace}.desktop; -in -{ +in { imports = [ inputs.grub2-themes.nixosModules.default ]; options.${namespace}.desktop = { use = mkOption { - type = nullOr (enum [ "plasma" "gamescope" "gnome" ]); + type = nullOr (enum ["plasma" "gamescope" "gnome" "cosmic"]); default = null; example = "plasma"; description = "Which desktop to enable"; @@ -20,11 +24,11 @@ in }; config = mkMerge [ - ({ + { services.displayManager = { enable = true; }; - }) + } # (mkIf (cfg.use != null) { # ${namespace}.desktop.${cfg.use}.enable = true; diff --git a/systems/x86_64-linux/manwe/default.nix b/systems/x86_64-linux/manwe/default.nix index 3839cc6..a2c478f 100644 --- a/systems/x86_64-linux/manwe/default.nix +++ b/systems/x86_64-linux/manwe/default.nix @@ -20,7 +20,7 @@ animated = true; }; - desktop.use = "plasma"; + desktop.use = "cosmic"; application = { steam.enable = true; From cfd5a40d3cb797a26191f39d8dfa06b96f7124a4 Mon Sep 17 00:00:00 2001 From: chris Date: Sun, 28 Dec 2025 20:38:16 +0000 Subject: [PATCH 034/108] chore(secrets): set secret "qbittorrent/password" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 4c9cc8d..0d7eb55 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -54,7 +54,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-12-11T22:47:01Z" - mac: ENC[AES256_GCM,data:7d8MOO5luFUGI2DhHUQRoBG4097/KnKvmj32vbdgoPFQWuLmePAdhJJ+n2T3Si89eLNoZVpwd2gvunSLiKG+rTyvVxf3jx1bOuGOhL2VlbbnLHiJPhX6Wi3NhRRUDqWl3oNcq/G9f6Y+fJ5R+Cofkov+aTPhIAM+Ceq/7Bf+J/M=,iv:zpf8r+W2QjUAmULnjzEQdESF4cuNWa3aMIVo6FPe/YI=,tag:z5YFIqlS1URIRsnqjFLquw==,type:str] + lastmodified: "2025-12-28T20:38:15Z" + mac: ENC[AES256_GCM,data:4JHRUEwCDA6FDQvyPluuM0qN5msaeQl8xCuy4hdBzcIzCSlZXWaiN6HDUZk9Nov34QIa9eXMdVwPg3jnnnQuQFpuiQuWn8NGuIueIeYf1L01Xf3aF+grNBsQFJqR4BAftkCwCFCvfayAL9fUjg4zQ9u67MJhKazgkYerBwih8dc=,iv:yLUVGhtOgJwDA13WGrzvx4qDzOTvtUe1hiEzdLnFyi0=,tag:gBwqx1RXzJqWbIKD0P9abQ==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From d2ee47f78c0ea52c7d3f3dffe77ef1c68b560b3b Mon Sep 17 00:00:00 2001 From: chris Date: Sun, 28 Dec 2025 21:28:31 +0000 Subject: [PATCH 035/108] chore(secrets): set secret "whisparr/apikey" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 0d7eb55..646c768 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -34,6 +34,8 @@ sabnzbd: password: ENC[AES256_GCM,data:flw8AahqO1Mx,iv:Qhu8iVWMzzqy18y8dj3aHoBnSZatm74/tYvZ456l2sA=,tag:sCYBdw7kD0zJZFFr5EyPIQ==,type:str] username: ENC[AES256_GCM,data:IboJ8WDWuVNgvrk7c3V8I5S6Xg==,iv:BRohMuQFQz2S+HFasIaok6npT3C5v/SlhAhbLQXfB0s=,tag:M3/u0WBQ3AufHqe4DCtsrA==,type:str] apikey: ENC[AES256_GCM,data:j5sPXKbBhMdNHOuoTfZ+c8nGu5JameOgK2z428iLdP01Hi6MvHVaN8Zs8YxMoSBtOjdtIEC8MS+3m1S1rU/P4pCRfZpK5ua1DBHq4l0xROUqokFWjDcAmJJv3pYXl0cQxQcGKQ==,iv:v5hu3gmO1Zn1FfXkHLPGN9f7JOcQjzoQahdqJwfM+xY=,tag:uI1LFcTgcyRgAaTJ1kzKow==,type:str] +whisparr: + apikey: ENC[AES256_GCM,data:kIGCsd4mszm90PoQMzlSEBKw9Ow0GvP1qdLtwXYKkAb6b65l89v8lMWJ2X1MyD2gJX+P+Bv1F/2BSjUFXErq/UYnp4dAjwKi/ezGCbhjMutDM1FvwFWEHRnR3gjd9uXPWJ8Xhg==,iv:98aPQlcZHJovpnzACDs6RtKblLnHg6wyi+Er5DAowj8=,tag:Tl8jz/pWYWAtBCfoztKdyw==,type:str] sops: age: - recipient: age19qfpf980tadguqq44zf6xwvjvl428dyrj46ha3n6aeqddwhtnuqqml7etq @@ -54,7 +56,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-12-28T20:38:15Z" - mac: ENC[AES256_GCM,data:4JHRUEwCDA6FDQvyPluuM0qN5msaeQl8xCuy4hdBzcIzCSlZXWaiN6HDUZk9Nov34QIa9eXMdVwPg3jnnnQuQFpuiQuWn8NGuIueIeYf1L01Xf3aF+grNBsQFJqR4BAftkCwCFCvfayAL9fUjg4zQ9u67MJhKazgkYerBwih8dc=,iv:yLUVGhtOgJwDA13WGrzvx4qDzOTvtUe1hiEzdLnFyi0=,tag:gBwqx1RXzJqWbIKD0P9abQ==,type:str] + lastmodified: "2025-12-28T21:28:31Z" + mac: ENC[AES256_GCM,data:vkGMgBkzmA2+xRIOfgUE01XG6jvTMTpm1vWXVHdZ5xE27s2mn8i6C64t1cia0n413qlKLB3y5qcbiHdRVhdLUoZFdBgFTjfixyIXOKZeVJskjJEqg2L0wZGtYIO8Y2KrfPb925qOffr7p0NcMf4c+d6bIqxHFEGb+jR/aWDOMNo=,iv:PK1FHycgOj2wtJt1UfWEAe0mKSBVksu8KWUxljSp2oo=,tag:F/xAAxJLUDqW9Dnwgrd0Rg==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From ab9c4b4c14856ffeb6e7c9c19fb11a59eec04370 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Wed, 31 Dec 2025 00:41:52 +0100 Subject: [PATCH 036/108] . --- .just/machine.just | 2 +- modules/nixos/desktop/cosmic/default.nix | 10 +- .../services/communication/matrix/default.nix | 8 +- modules/nixos/services/media/default.nix | 35 --- .../nixos/services/media/glance/default.nix | 10 - .../nixos/services/media/jellyfin/default.nix | 50 +++++ .../nixos/services/media/servarr/default.nix | 200 ++++++++++++++---- modules/nixos/services/media/servarr/lib.nix | 2 + systems/x86_64-linux/manwe/default.nix | 1 + systems/x86_64-linux/ulmo/default.nix | 28 ++- 10 files changed, 243 insertions(+), 103 deletions(-) create mode 100644 modules/nixos/services/media/jellyfin/default.nix create mode 100644 modules/nixos/services/media/servarr/lib.nix diff --git a/.just/machine.just b/.just/machine.just index 207185a..d07986b 100644 --- a/.just/machine.just +++ b/.just/machine.just @@ -8,4 +8,4 @@ [no-exit-message] @update machine: just assert '-d "../systems/x86_64-linux/{{ machine }}"' "Machine {{ machine }} does not exist, must be one of: $(ls ../systems/x86_64-linux/ | sed ':a;N;$!ba;s/\n/, /g')" - nixos-rebuild switch -L --sudo --target-host {{ machine }} --flake ..#{{ machine }} + nixos-rebuild switch -L --sudo --target-host {{ machine }} --flake ..#{{ machine }} --log-format internal-json -v |& nom --json diff --git a/modules/nixos/desktop/cosmic/default.nix b/modules/nixos/desktop/cosmic/default.nix index ff4d047..cba6955 100644 --- a/modules/nixos/desktop/cosmic/default.nix +++ b/modules/nixos/desktop/cosmic/default.nix @@ -5,7 +5,7 @@ inputs, ... }: let - inherit (lib) mkIf mkEnableOption mkForce; + inherit (lib) mkIf mkEnableOption; cfg = config.${namespace}.desktop.cosmic; in { @@ -19,13 +19,7 @@ in { config = mkIf cfg.enable { services = { - displayManager = { - cosmic-greeter.enable = true; - autoLogin = { - enable = true; - user = "chris"; - }; - }; + displayManager.cosmic-greeter.enable = true; desktopManager.cosmic.enable = true; }; }; diff --git a/modules/nixos/services/communication/matrix/default.nix b/modules/nixos/services/communication/matrix/default.nix index 33af8e4..ccdbbaa 100644 --- a/modules/nixos/services/communication/matrix/default.nix +++ b/modules/nixos/services/communication/matrix/default.nix @@ -52,11 +52,15 @@ in { # Since we'll be using OIDC for auth disable all local options enable_registration = false; enable_registration_without_verification = false; - password_config.enabled = false; + password_config.enabled = true; backchannel_logout_enabled = true; + experimental_features = { + msc2965_enabled = true; + }; + sso = { - client_whitelist = ["http://[::1]:9092"]; + client_whitelist = ["http://[::1]:9092/" "https://auth.kruining.eu/"]; update_profile_information = true; }; diff --git a/modules/nixos/services/media/default.nix b/modules/nixos/services/media/default.nix index 79d2307..c10a08e 100644 --- a/modules/nixos/services/media/default.nix +++ b/modules/nixos/services/media/default.nix @@ -35,13 +35,6 @@ in { #========================================================================= environment.systemPackages = with pkgs; [ podman-tui - jellyfin - jellyfin-web - jellyfin-ffmpeg - jellyseerr - mediainfo - id3v2 - yt-dlp ]; #========================================================================= @@ -56,9 +49,6 @@ in { }; systemd.tmpfiles.rules = [ - # "d '${cfg.path}/series' 0770 ${cfg.user} ${cfg.group} - -" - # "d '${cfg.path}/movies' 0770 ${cfg.user} ${cfg.group} - -" - # "d '${cfg.path}/music' 0770 ${cfg.user} ${cfg.group} - -" "d '${cfg.path}/qbittorrent' 0770 ${cfg.user} ${cfg.group} - -" "d '${cfg.path}/sabnzbd' 0770 ${cfg.user} ${cfg.group} - -" "d '${cfg.path}/downloads/incomplete' 0770 ${cfg.user} ${cfg.group} - -" @@ -77,34 +67,9 @@ in { listenPort = 2005; }; - flaresolverr = { - enable = true; - openFirewall = true; - port = 2007; - }; - - # port is harcoded in nixpkgs module - jellyfin = { - enable = true; - openFirewall = true; - user = cfg.user; - group = cfg.group; - }; - postgresql = { enable = true; }; - - caddy = { - enable = true; - virtualHosts = { - "jellyfin.kruining.eu".extraConfig = '' - reverse_proxy http://[::1]:8096 - ''; - }; - }; }; - - systemd.services.jellyfin.serviceConfig.killSignal = lib.mkForce "SIGKILL"; }; } diff --git a/modules/nixos/services/media/glance/default.nix b/modules/nixos/services/media/glance/default.nix index 333035d..6af52ef 100644 --- a/modules/nixos/services/media/glance/default.nix +++ b/modules/nixos/services/media/glance/default.nix @@ -130,16 +130,6 @@ in { } ]; } - { - type = "videos"; - channels = [ - "UCXuqSBlHAE6Xw-yeJA0Tunw" # Linus Tech Tips - "UCR-DXc1voovS8nhAvccRZhg" # Jeff Geerling - "UCsBjURrPoezykLs9EqgamOA" # Fireship - "UCBJycsmduvYEL83R_U4JriQ" # Marques Brownlee - "UCHnyfMqiRRG1u-2MsSQLbXA" # Veritasium - ]; - } ]; } diff --git a/modules/nixos/services/media/jellyfin/default.nix b/modules/nixos/services/media/jellyfin/default.nix new file mode 100644 index 0000000..d4323f3 --- /dev/null +++ b/modules/nixos/services/media/jellyfin/default.nix @@ -0,0 +1,50 @@ +{ + pkgs, + config, + lib, + namespace, + inputs, + system, + ... +}: let + inherit (builtins) toString; + inherit (lib) mkIf mkEnableOption mkOption types; + + cfg = config.${namespace}.services.media.jellyfin; +in { + options.${namespace}.services.media.jellyfin = { + enable = mkEnableOption "Enable jellyfin server"; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + jellyfin + jellyfin-web + jellyfin-ffmpeg + mediainfo + id3v2 + yt-dlp + ]; + + services = { + # port is harcoded in nixpkgs module + jellyfin = { + enable = true; + openFirewall = true; + user = "media"; + group = "media"; + }; + + caddy = { + enable = true; + virtualHosts = { + "jellyfin.kruining.eu".extraConfig = '' + reverse_proxy http://[::1]:8096 + ''; + }; + }; + }; + + systemd.services.jellyfin.serviceConfig.killSignal = lib.mkForce "SIGKILL"; + }; +} diff --git a/modules/nixos/services/media/servarr/default.nix b/modules/nixos/services/media/servarr/default.nix index 057b810..bc911f7 100644 --- a/modules/nixos/services/media/servarr/default.nix +++ b/modules/nixos/services/media/servarr/default.nix @@ -11,6 +11,7 @@ inherit (lib) mkIf mkEnableOption mkOption types; cfg = config.${namespace}.services.media.servarr; + servarr = import ./lib.nix {inherit lib;}; anyEnabled = cfg |> lib.attrNames |> lib.length |> (l: l > 0); in { options.${namespace}.services.media = { @@ -68,7 +69,7 @@ in { }; }; } - // (lib.optionalAttrs (service != "prowlarr") { + // (lib.optionalAttrs (lib.elem service ["radarr" "sonarr" "lidarr" "whisparr"]) { user = service; group = "media"; }); @@ -104,6 +105,12 @@ in { group = "media"; }; + flaresolverr = { + enable = true; + openFirewall = true; + port = 2007; + }; + postgresql = { ensureDatabases = cfg |> lib.attrNames; ensureUsers = @@ -129,6 +136,7 @@ in { }: (mkIf enable { "${service}ApplyTerraform" = let config' = config; + lib' = lib; terraformConfiguration = inputs.terranix.lib.terranixConfiguration { inherit system; @@ -140,12 +148,28 @@ in { ... }: { config = { - variable = { - api_key = { - type = "string"; - description = "${service} api key"; - }; - }; + variable = + cfg + |> lib'.mapAttrsToList (s: _: { + "${s}_api_key" = { + type = "string"; + description = "${s} API key"; + }; + }) + |> lib'.concat [ + { + qbittorrent_api_key = { + type = "string"; + description = "qbittorrent api key"; + }; + + sabnzbd_api_key = { + type = "string"; + description = "sabnzbd api key"; + }; + } + ] + |> lib'.mkMerge; terraform.required_providers.${service} = { source = "devopsarr/${service}"; @@ -164,40 +188,116 @@ in { provider.${service} = { url = "http://127.0.0.1:${toString port}"; - api_key = lib.tfRef "var.api_key"; + api_key = lib.tfRef "var.${service}_api_key"; }; - resource = { - "${service}_root_folder" = mkIf (lib.elem service ["radarr" "sonarr" "whisparr"]) ( - rootFolders - |> lib.imap (i: f: lib.nameValuePair "local${toString i}" {path = f;}) - |> lib.listToAttrs - ); + resource = + { + "${service}_root_folder" = mkIf (lib.elem service ["radarr" "sonarr" "whisparr"]) ( + rootFolders + |> lib.imap (i: f: lib.nameValuePair "local${toString i}" {path = f;}) + |> lib.listToAttrs + ); - "${service}_download_client_qbittorrent" = mkIf (lib.elem service ["radarr" "sonarr" "lidarr" "whisparr"]) { - "main" = { - name = "qBittorrent"; - enable = true; - priority = 1; - host = "localhost"; - username = "admin"; - password = "poChieN5feeph0igeaCadeJ9Xux0ohmuy6ruH5ieThaPheib3iuzoo0ahw1aiceif1feegioh9Aimau0pai5thoh5ieH0aechohw"; - url_base = "/"; - port = 2008; + "${service}_download_client_qbittorrent" = mkIf (lib.elem service ["radarr" "sonarr" "lidarr" "whisparr"]) { + "main" = { + name = "qBittorrent"; + enable = true; + priority = 1; + host = "localhost"; + username = "admin"; + password = lib.tfRef "var.qbittorrent_api_key"; + # password = "poChieN5feeph0igeaCadeJ9Xux0ohmuy6ruH5ieThaPheib3iuzoo0ahw1aiceif1feegioh9Aimau0pai5thoh5ieH0aechohw"; + url_base = "/"; + port = 2008; + }; }; - }; - # "${service}_download_client_sabnzbd" = mkIf (lib.elem service ["radarr" "sonarr" "lidarr" "whisparr"]) { - # "main" = { - # name = "SABnzbd"; - # enable = true; - # priority = 1; - # host = "localhost"; - # url_base = "/"; - # port = 8080; - # }; - # }; - }; + "${service}_download_client_sabnzbd" = mkIf (lib.elem service ["radarr" "sonarr" "lidarr" "whisparr"]) { + "main" = { + name = "SABnzbd"; + enable = true; + priority = 1; + host = "localhost"; + api_key = lib.tfRef "var.sabnzbd_api_key"; + url_base = "/"; + port = 8080; + }; + }; + } + // (lib.optionalAttrs (service == "prowlarr") ( + cfg + |> lib'.filterAttrs (s: _: lib'.elem s ["radarr" "sonarr" "lidarr" "whisparr"]) + |> lib'.mapAttrsToList (s: {port, ...}: { + "prowlarr_application_${s}"."main" = let + p = cfg.prowlarr.port or config'.services.prowlarr.settings.server.port or 9696; + in { + name = s; + sync_level = "addOnly"; + base_url = "http://localhost:${toString port}"; + prowlarr_url = "http://localhost:${toString p}"; + api_key = lib.tfRef "var.${s}_api_key"; + # sync_categories = [3000 3010 3030]; + }; + }) + |> lib'.concat [ + { + "prowlarr_indexer" = { + "nyaa" = { + enable = true; + + app_profile_id = 1; + priority = 1; + + name = "Nyaa"; + implementation = "nyaa"; + config_contract = "nyaa_settings"; + protocol = "torrent"; + + fields = [ + { + name = "targetType"; + value = ""; + } + ]; + }; + + "nzbgeek" = { + enable = true; + + app_profile_id = 2; + priority = 1; + + name = "NZBgeek"; + implementation = "nzbgeek"; + config_contract = "nzbgeek_settings"; + protocol = "torrent"; + + fields = [ + ]; + }; + + # "nzbgeek" = { + # enable = true; + + # app_profile_id = 1; + # name = "NZBgeek"; + # implementation = "nzbgeek"; + # config_contract = "nzbgeek_settings"; + # protocol = "torrent"; + + # fields = [ + # # { + # # name = ""; + # # value = ""; + # # } + # ]; + # }; + }; + } + ] + |> lib'.mkMerge + )); }; }) ]; @@ -242,7 +342,7 @@ in { then "plan" else "apply -auto-approve" } \ - -var-file='${config.sops.templates."${service}/config.tfvars".path}' + -var-file='${config.sops.templates."servarr/config.tfvars".path}' ''; serviceConfig = { @@ -295,26 +395,34 @@ in { ${lib.toUpper service}__AUTH__APIKEY="${config.sops.placeholder."${service}/apikey"}" ''; }; - - "${service}/config.tfvars" = { - owner = service; - group = "media"; - restartUnits = ["${service}.service"]; - content = '' - api_key = "${config.sops.placeholder."${service}/apikey"}" - ''; - }; }; })) |> lib.concat [ { secrets = { + "qbittorrent/password" = {}; "sabnzbd/apikey" = {}; "sabnzbd/sunnyweb/username" = {}; "sabnzbd/sunnyweb/password" = {}; }; templates = { + "servarr/config.tfvars" = { + owner = "media"; + group = "media"; + mode = "0440"; + restartUnits = cfg |> lib.attrNames |> lib.map (s: "${s}.service"); + content = '' + ${ + cfg + |> lib.attrNames + |> lib.map (s: "${s}_api_key = \"${config.sops.placeholder."${s}/apikey"}\"") + |> lib.join "\n" + } + qbittorrent_api_key = "${config.sops.placeholder."qbittorrent/password"}" + sabnzbd_api_key = "${config.sops.placeholder."sabnzbd/apikey"}" + ''; + }; "sabnzbd/config.ini" = { owner = "sabnzbd"; group = "media"; diff --git a/modules/nixos/services/media/servarr/lib.nix b/modules/nixos/services/media/servarr/lib.nix new file mode 100644 index 0000000..8ee412b --- /dev/null +++ b/modules/nixos/services/media/servarr/lib.nix @@ -0,0 +1,2 @@ +{lib, ...}: { +} diff --git a/systems/x86_64-linux/manwe/default.nix b/systems/x86_64-linux/manwe/default.nix index a2c478f..84b180b 100644 --- a/systems/x86_64-linux/manwe/default.nix +++ b/systems/x86_64-linux/manwe/default.nix @@ -24,6 +24,7 @@ application = { steam.enable = true; + zen.enable = true; }; editor = { diff --git a/systems/x86_64-linux/ulmo/default.nix b/systems/x86_64-linux/ulmo/default.nix index e661dd8..4203859 100644 --- a/systems/x86_64-linux/ulmo/default.nix +++ b/systems/x86_64-linux/ulmo/default.nix @@ -38,6 +38,31 @@ ''; }; + # virtualisation = { + # containers.enable = true; + # podman = { + # enable = true; + # dockerCompat = true; + # }; + + # oci-containers = { + # backend = "podman"; + # containers = { + # homey = { + # image = "ghcr.io/athombv/homey-shs:latest"; + # autoStart = true; + # privileged = true; + # volumes = [ + # "/home/chris/.homey-shs:/homey/user" + # ]; + # ports = [ + # "4859:4859" + # ]; + # }; + # }; + # }; + # }; + sneeuwvlok = { services = { backup.borg.enable = true; @@ -170,6 +195,7 @@ media.glance.enable = true; media.mydia.enable = true; media.nfs.enable = true; + media.jellyfin.enable = true; media.servarr = { radarr = { enable = true; @@ -199,7 +225,7 @@ prowlarr = { enable = true; - debug = true; + # debug = true; port = 2004; }; }; From 36f2f501fc46a667edda189aebef849bc270d429 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Wed, 31 Dec 2025 01:04:03 +0100 Subject: [PATCH 037/108] . --- systems/x86_64-linux/manwe/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/systems/x86_64-linux/manwe/default.nix b/systems/x86_64-linux/manwe/default.nix index 84b180b..8cf5cc1 100644 --- a/systems/x86_64-linux/manwe/default.nix +++ b/systems/x86_64-linux/manwe/default.nix @@ -24,7 +24,7 @@ application = { steam.enable = true; - zen.enable = true; + # zen.enable = true; }; editor = { From 18b8e43a7596fd9486831690de83704fbbca0282 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Wed, 31 Dec 2025 01:10:31 +0100 Subject: [PATCH 038/108] aaaargh --- systems/x86_64-linux/manwe/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/systems/x86_64-linux/manwe/default.nix b/systems/x86_64-linux/manwe/default.nix index 8cf5cc1..2270640 100644 --- a/systems/x86_64-linux/manwe/default.nix +++ b/systems/x86_64-linux/manwe/default.nix @@ -20,7 +20,7 @@ animated = true; }; - desktop.use = "cosmic"; + desktop.use = "plasma"; application = { steam.enable = true; From b42f182de40821e7231fddca4e5c956531fc89ef Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Wed, 31 Dec 2025 01:46:41 +0100 Subject: [PATCH 039/108] chore: update dependencies --- flake.lock | 132 +++++++++++++++++++++++------------------------------ 1 file changed, 57 insertions(+), 75 deletions(-) diff --git a/flake.lock b/flake.lock index 004e726..9498a01 100644 --- a/flake.lock +++ b/flake.lock @@ -149,11 +149,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1765983453, - "narHash": "sha256-cH32B1gu+/AQA8arQScPStZhmT73jDnrzSejuNR//F0=", + "lastModified": 1761426037, + "narHash": "sha256-bP3CG+N8nmK7zyg6EN96RaLrB3ZZ6q6tMaTsVuwidcA=", "owner": "emmanuelrosa", "repo": "erosanix", - "rev": "b742cf8ae93417efb2c1bbcc70146a664126d8be", + "rev": "61c63c75dd712afd2a7ba84225a25fef801b5718", "type": "github" }, "original": { @@ -170,11 +170,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1765435813, - "narHash": "sha256-C6tT7K1Lx6VsYw1BY5S3OavtapUvEnDQtmQB5DSgbCc=", + "lastModified": 1761547629, + "narHash": "sha256-4OH1CVm2PdjKRqEJ3RLfkQMDSBdn7VId6iyYCwKOK+U=", "owner": "nix-community", "repo": "fenix", - "rev": "6399553b7a300c77e7f07342904eb696a5b6bf9d", + "rev": "d82a7c64ea441e397914577c9a18f2867e5b364b", "type": "github" }, "original": { @@ -190,11 +190,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1766067704, - "narHash": "sha256-k8JGGuFfml1qsMI5AW1f1+2+6S3YAHkw+AHXACkZ+l8=", + "lastModified": 1761589789, + "narHash": "sha256-rIV3APsvwxQbqxJgrJxeHpuXYxg/J1IQ/U0+dBb++VE=", "owner": "nix-community", "repo": "flake-firefox-nightly", - "rev": "59d2f3f13afbf4fc88431316c0eef124f1e6e822", + "rev": "0694df33c2367366cb047d5686b2b01bb1de63ab", "type": "github" }, "original": { @@ -574,11 +574,11 @@ "rust-overlay": "rust-overlay" }, "locked": { - "lastModified": 1765998060, - "narHash": "sha256-RGluSceSmt/1wZVwnkrLGc+oImO94o723fNPTEJm25w=", + "lastModified": 1761593571, + "narHash": "sha256-rdrHiUJ870eUYhgH8e1QgSkdtkBW+4RWQNukxO7R9J0=", "owner": "himmelblau-idm", "repo": "himmelblau", - "rev": "3ba7e3c89968d1a8f70c14550df58bdd997b998d", + "rev": "1992b35b7d6c188db5323166eaa26c36e6588c66", "type": "github" }, "original": { @@ -594,11 +594,11 @@ ] }, "locked": { - "lastModified": 1765980955, - "narHash": "sha256-rB45jv4uwC90vM9UZ70plfvY/2Kdygs+zlQ07dGQFk4=", + "lastModified": 1761584077, + "narHash": "sha256-dISPEZahlfs5K6d58zR4akRRyogfE9P4WSyPPNT7HiE=", "owner": "nix-community", "repo": "home-manager", - "rev": "89c9508bbe9b40d36b3dc206c2483ef176f15173", + "rev": "e82585308aef3d4cc2c36c7b6946051c8cdf24ef", "type": "github" }, "original": { @@ -636,11 +636,11 @@ ] }, "locked": { - "lastModified": 1766067735, - "narHash": "sha256-cRC/rOYRtZNzc5y9nTccozyo/mkI4/1eFE33Aqgs+SQ=", + "lastModified": 1761376732, + "narHash": "sha256-wavx9gROyuRZKSvPCCBh78gOur7o88ndRi545njNRrM=", "owner": "Jovian-Experiments", "repo": "Jovian-NixOS", - "rev": "34a16089be30f77ac9444907ec97c02b4b711896", + "rev": "8bef482d65425d0cff6b20c11a5f054f85569a38", "type": "github" }, "original": { @@ -770,11 +770,11 @@ "nixpkgs": "nixpkgs_6" }, "locked": { - "lastModified": 1766023574, - "narHash": "sha256-vx7KhTqR/UBnBUXAei3DKXJ4Nq3p7yLw+kZ03/inm8I=", + "lastModified": 1761530861, + "narHash": "sha256-VMhre9pdUAT6TDo0KV1kOjtZywCEoBowKRYSaa7KHP0=", "owner": "Infinidoge", "repo": "nix-minecraft", - "rev": "5e0cae13ca72d3e4ef0f101b01725e25441c4ebd", + "rev": "c90769ae2c7d46fdadcdb81e09a97137b3b87891", "type": "github" }, "original": { @@ -870,11 +870,11 @@ ] }, "locked": { - "lastModified": 1765841014, - "narHash": "sha256-55V0AJ36V5Egh4kMhWtDh117eE3GOjwq5LhwxDn9eHg=", + "lastModified": 1761563673, + "narHash": "sha256-d+1TpVAmRjcNBfjZsh2yQSdwUfN7Xgz1blJ185g73+A=", "owner": "nix-community", "repo": "nixos-wsl", - "rev": "be4af8042e7a61fa12fda58fe9a3b3babdefe17b", + "rev": "a518cf710e5ebb935518dc7ac98e07e7ee5014c3", "type": "github" }, "original": { @@ -948,11 +948,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1766035038, - "narHash": "sha256-tJ8RIG8JiMV9NGYWegYw1bEO/Ja09mH1y7RhuviEN78=", + "lastModified": 1761544814, + "narHash": "sha256-t5f0A+2MtSWTfA6hzMNiotpIMGLlSQF2JnK9m6nkzIY=", "owner": "nixos", "repo": "nixpkgs", - "rev": "da5cc354cf31d8514d763c39f5a685da3fe1eb3e", + "rev": "e5aa45ed6c45058ec109658b2b7352a9a062cdf3", "type": "github" }, "original": { @@ -980,11 +980,11 @@ }, "nixpkgs_4": { "locked": { - "lastModified": 1766068349, - "narHash": "sha256-pb4NHaLbyEo5C+h1XfQAkdI/ceKrKO1YvUEZGLrdIMo=", + "lastModified": 1761595987, + "narHash": "sha256-rIUh2oI9qfBmusxTlsqhfAdDU6IAl8OcDAu5OXNyeLc=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "fe17732c30a65edbd3f101db6aa6f76a38e21ac2", + "rev": "ca1d8cb9a4bd42196e73f341d66d0805bce8b1c8", "type": "github" }, "original": { @@ -1028,11 +1028,11 @@ }, "nixpkgs_7": { "locked": { - "lastModified": 1765779637, - "narHash": "sha256-KJ2wa/BLSrTqDjbfyNx70ov/HdgNBCBBSQP3BIzKnv4=", + "lastModified": 1761373498, + "narHash": "sha256-Q/uhWNvd7V7k1H1ZPMy/vkx3F8C13ZcdrKjO7Jv7v0c=", "owner": "nixos", "repo": "nixpkgs", - "rev": "1306659b587dc277866c7b69eb97e5f07864d8c4", + "rev": "6a08e6bb4e46ff7fcbb53d409b253f6bad8a28ce", "type": "github" }, "original": { @@ -1044,11 +1044,11 @@ }, "nixpkgs_8": { "locked": { - "lastModified": 1764242076, - "narHash": "sha256-sKoIWfnijJ0+9e4wRvIgm/HgE27bzwQxcEmo2J/gNpI=", + "lastModified": 1760596604, + "narHash": "sha256-J/i5K6AAz/y5dBePHQOuzC7MbhyTOKsd/GLezSbEFiM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "2fad6eac6077f03fe109c4d4eb171cf96791faa4", + "rev": "3cbe716e2346710d6e1f7c559363d14e11c32a43", "type": "github" }, "original": { @@ -1109,11 +1109,11 @@ "systems": "systems_5" }, "locked": { - "lastModified": 1765894398, - "narHash": "sha256-vZmKngMAaZ38mUjWeXavagrCE5f4h4s9yIShf2q75I4=", + "lastModified": 1761486540, + "narHash": "sha256-O0VNqERaZ1H+4P3XwHNd8wVCqUcGtMPJT1z4cJodRFc=", "owner": "notashelf", "repo": "nvf", - "rev": "cd81bbb904571b538397d72a29e4c84b98480ee1", + "rev": "4b904de36157035fa3dddf0312a4242e5d0d9bd0", "type": "github" }, "original": { @@ -1132,11 +1132,11 @@ ] }, "locked": { - "lastModified": 1763909441, - "narHash": "sha256-56LwV51TX/FhgX+5LCG6akQ5KrOWuKgcJa+eUsRMxsc=", + "lastModified": 1761078382, + "narHash": "sha256-JNJesbe9MMN1Brq41BHEpuH+Z+Zg74y/nI5AFZX84Vw=", "owner": "nix-community", "repo": "plasma-manager", - "rev": "b24ed4b272256dfc1cc2291f89a9821d5f9e14b4", + "rev": "27dfa61b64d0cdb8e4ba6f3aaa4d4e067d64cb5c", "type": "github" }, "original": { @@ -1174,11 +1174,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1765400135, - "narHash": "sha256-D3+4hfNwUhG0fdCpDhOASLwEQ1jKuHi4mV72up4kLQM=", + "lastModified": 1761500479, + "narHash": "sha256-syeBTCCU96qPJHcVpwHeCwmPCiLTDHHgYQYhpZ0iwLo=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "fface27171988b3d605ef45cf986c25533116f7e", + "rev": "049767e6faa84b2d1a951d8f227e6ebd99d728a2", "type": "github" }, "original": { @@ -1196,11 +1196,11 @@ ] }, "locked": { - "lastModified": 1759977258, - "narHash": "sha256-hOxEFSEBoqDmJb7BGX1CzT1gvUPK6r+Qs+n3IxBgfTs=", + "lastModified": 1761532837, + "narHash": "sha256-78mCSQgC/a6/0vWYrvE/g9E3gGsJLyBBGtmHe3ZOLG4=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "1d0c6173f57d07db7957b50e799240d4f2d7520f", + "rev": "4f5f89f1cfd8553b1285a4a0879ea1b2b05ad286", "type": "github" }, "original": { @@ -1239,29 +1239,11 @@ ] }, "locked": { - "lastModified": 1765836173, - "narHash": "sha256-hWRYfdH2ONI7HXbqZqW8Q1y9IRbnXWvtvt/ONZovSNY=", + "lastModified": 1760998189, + "narHash": "sha256-ee2e1/AeGL5X8oy/HXsZQvZnae6XfEVdstGopKucYLY=", "owner": "Mic92", "repo": "sops-nix", - "rev": "443a7f2e7e118c4fc63b7fae05ab3080dd0e5c63", - "type": "github" - }, - "original": { - "owner": "Mic92", - "repo": "sops-nix", - "type": "github" - } - }, - "sops-nix_2": { - "inputs": { - "nixpkgs": "nixpkgs_10" - }, - "locked": { - "lastModified": 1765836173, - "narHash": "sha256-hWRYfdH2ONI7HXbqZqW8Q1y9IRbnXWvtvt/ONZovSNY=", - "owner": "Mic92", - "repo": "sops-nix", - "rev": "443a7f2e7e118c4fc63b7fae05ab3080dd0e5c63", + "rev": "5a7d18b5c55642df5c432aadb757140edfeb70b3", "type": "github" }, "original": { @@ -1289,11 +1271,11 @@ "tinted-zed": "tinted-zed" }, "locked": { - "lastModified": 1765897595, - "narHash": "sha256-NgTRxiEC5y96zrhdBygnY+mSzk5FWMML39PcRGVJmxg=", + "lastModified": 1761028816, + "narHash": "sha256-s1XiIeJHpODVWfzsPaK9e21iz1dQSCU3H4/1OxOsyps=", "owner": "nix-community", "repo": "stylix", - "rev": "e6829552d4bb659ebab00f08c61d8c62754763f3", + "rev": "b81dc0a385443099e7d231fe6275189e32c3b760", "type": "github" }, "original": { @@ -1554,11 +1536,11 @@ ] }, "locked": { - "lastModified": 1766032508, - "narHash": "sha256-7MHR94mOoa5/s4NBrpsXWaNNzrZyRC0OwRwEobp1wzI=", + "lastModified": 1761535208, + "narHash": "sha256-E1PobJMiFmVUX2YdqYk/MpKb0LXavOYvlg8DCBBzlHc=", "owner": "0xc000022070", "repo": "zen-browser-flake", - "rev": "a7f58a9e3481804915d75a9c86527bca6d9dafb3", + "rev": "79a94872a3e6993a051c4e22a2dcb02c1d088acf", "type": "github" }, "original": { From e731b476b1ca2928d6b74a7962c96bf59c102763 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Wed, 31 Dec 2025 01:19:31 +0100 Subject: [PATCH 040/108] revert --- systems/x86_64-linux/manwe/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/systems/x86_64-linux/manwe/default.nix b/systems/x86_64-linux/manwe/default.nix index 2270640..8cf5cc1 100644 --- a/systems/x86_64-linux/manwe/default.nix +++ b/systems/x86_64-linux/manwe/default.nix @@ -20,7 +20,7 @@ animated = true; }; - desktop.use = "plasma"; + desktop.use = "cosmic"; application = { steam.enable = true; From 168977538edbe1f30f3253dd9c1b805bdedc4229 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Wed, 31 Dec 2025 01:47:37 +0100 Subject: [PATCH 041/108] revert --- systems/x86_64-linux/manwe/default.nix | 1 - 1 file changed, 1 deletion(-) diff --git a/systems/x86_64-linux/manwe/default.nix b/systems/x86_64-linux/manwe/default.nix index 8cf5cc1..a2c478f 100644 --- a/systems/x86_64-linux/manwe/default.nix +++ b/systems/x86_64-linux/manwe/default.nix @@ -24,7 +24,6 @@ application = { steam.enable = true; - # zen.enable = true; }; editor = { From 6fb898e01c1ede8c2b42ca97252aec06fa638e7b Mon Sep 17 00:00:00 2001 From: chris Date: Wed, 31 Dec 2025 01:36:28 +0000 Subject: [PATCH 042/108] chore: update dependencies --- flake.lock | 235 +++++++++++++++++++++++++++-------------------------- 1 file changed, 120 insertions(+), 115 deletions(-) diff --git a/flake.lock b/flake.lock index 9498a01..0aebd9e 100644 --- a/flake.lock +++ b/flake.lock @@ -84,11 +84,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1766058975, - "narHash": "sha256-HBnRRq9wLq7UfJxMM55wR10lZFK1F0lNyRgUwwOby6s=", - "rev": "9032d11a0e31641808ef1427150aac0f40e2e0b9", + "lastModified": 1767139704, + "narHash": "sha256-ftRAe4dLMx+D3ArFtEJqoTsV0Y1vegFKx4YZb1/v7dU=", + "rev": "7fa4abb7b016c3a7ae3f346784fac0298a9b14fb", "type": "tarball", - "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/9032d11a0e31641808ef1427150aac0f40e2e0b9.tar.gz" + "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/7fa4abb7b016c3a7ae3f346784fac0298a9b14fb.tar.gz" }, "original": { "type": "tarball", @@ -111,11 +111,11 @@ ] }, "locked": { - "lastModified": 1765768061, - "narHash": "sha256-RZ/ocDUJ3WPr2KcDc2MB6Fu+ZPqzwsMKQ16XxqrPi+o=", - "rev": "53351f9953ecf9dbe18795b4784abe53b14e6eee", + "lastModified": 1766977667, + "narHash": "sha256-LUALgG4ZpsA0k7pGYzMDto/r6T8aIPlYTok3lGlojjA=", + "rev": "3f852546b5d8bd2e9659a81c6b2cc14922e63a94", "type": "tarball", - "url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/53351f9953ecf9dbe18795b4784abe53b14e6eee.tar.gz" + "url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/3f852546b5d8bd2e9659a81c6b2cc14922e63a94.tar.gz" }, "original": { "type": "tarball", @@ -130,11 +130,11 @@ ] }, "locked": { - "lastModified": 1765794845, - "narHash": "sha256-YD5QWlGnusNbZCqR3pxG8tRxx9yUXayLZfAJRWspq2s=", + "lastModified": 1766150702, + "narHash": "sha256-P0kM+5o+DKnB6raXgFEk3azw8Wqg5FL6wyl9jD+G5a4=", "owner": "nix-community", "repo": "disko", - "rev": "7194cfe5b7a3660726b0fe7296070eaef601cae9", + "rev": "916506443ecd0d0b4a0f4cf9d40a3c22ce39b378", "type": "github" }, "original": { @@ -149,11 +149,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1761426037, - "narHash": "sha256-bP3CG+N8nmK7zyg6EN96RaLrB3ZZ6q6tMaTsVuwidcA=", + "lastModified": 1766957541, + "narHash": "sha256-5uwnfwFgK5UMwgC0eaLIGGNuQpWOEywUiexEnbqeOAs=", "owner": "emmanuelrosa", "repo": "erosanix", - "rev": "61c63c75dd712afd2a7ba84225a25fef801b5718", + "rev": "8c7d54b7d8879c14dfa914ece38815bf9c248f8b", "type": "github" }, "original": { @@ -170,11 +170,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1761547629, - "narHash": "sha256-4OH1CVm2PdjKRqEJ3RLfkQMDSBdn7VId6iyYCwKOK+U=", + "lastModified": 1767077117, + "narHash": "sha256-tmVJMQC4aNUCME3ofKP2wWEBizabxwFZfLpZSi0S/4Q=", "owner": "nix-community", "repo": "fenix", - "rev": "d82a7c64ea441e397914577c9a18f2867e5b364b", + "rev": "f69c299a340f95776ddcfecfc0b1f6183c0c298e", "type": "github" }, "original": { @@ -190,11 +190,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1761589789, - "narHash": "sha256-rIV3APsvwxQbqxJgrJxeHpuXYxg/J1IQ/U0+dBb++VE=", + "lastModified": 1767133124, + "narHash": "sha256-CQPz7FIUZsIX+6uJFBWMvm3VlwjRDunwgXYghHa/o+0=", "owner": "nix-community", "repo": "flake-firefox-nightly", - "rev": "0694df33c2367366cb047d5686b2b01bb1de63ab", + "rev": "a99b92ac7003944b11e5a3ee0a6db65383d9ed58", "type": "github" }, "original": { @@ -574,11 +574,11 @@ "rust-overlay": "rust-overlay" }, "locked": { - "lastModified": 1761593571, - "narHash": "sha256-rdrHiUJ870eUYhgH8e1QgSkdtkBW+4RWQNukxO7R9J0=", + "lastModified": 1766174774, + "narHash": "sha256-kLeXugTZLamNMh8oPg/nnkLQGH9XXgncTbx/0R7X+w4=", "owner": "himmelblau-idm", "repo": "himmelblau", - "rev": "1992b35b7d6c188db5323166eaa26c36e6588c66", + "rev": "fc297543cef873c5c4f4a8a8f0aa7d745361e63a", "type": "github" }, "original": { @@ -594,11 +594,11 @@ ] }, "locked": { - "lastModified": 1761584077, - "narHash": "sha256-dISPEZahlfs5K6d58zR4akRRyogfE9P4WSyPPNT7HiE=", + "lastModified": 1767104570, + "narHash": "sha256-GKgwu5//R+cLdKysZjGqvUEEOGXXLdt93sNXeb2M/Lk=", "owner": "nix-community", "repo": "home-manager", - "rev": "e82585308aef3d4cc2c36c7b6946051c8cdf24ef", + "rev": "e4e78a2cbeaddd07ab7238971b16468cc1d14daf", "type": "github" }, "original": { @@ -636,11 +636,11 @@ ] }, "locked": { - "lastModified": 1761376732, - "narHash": "sha256-wavx9gROyuRZKSvPCCBh78gOur7o88ndRi545njNRrM=", + "lastModified": 1767082077, + "narHash": "sha256-2tL1mRb9uFJThUNfuDm/ehrnPvImL/QDtCxfn71IEz4=", "owner": "Jovian-Experiments", "repo": "Jovian-NixOS", - "rev": "8bef482d65425d0cff6b20c11a5f054f85569a38", + "rev": "efd4b22e6fdc6d7fb4e186ae333a4b74e03da440", "type": "github" }, "original": { @@ -655,11 +655,11 @@ "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1765716362, - "narHash": "sha256-ZjICTdeEXwhioCJyYeEIrP5UlW6QZvP8rAAquNy+vso=", + "lastModified": 1766926104, + "narHash": "sha256-c5CozmannX3I/ax0Ig9z/QjXRuNY/j3XTOx6KXvMUs4=", "owner": "nix-community", "repo": "lib-aggregate", - "rev": "b70d4898e9a4d2ad2d3ff870b9674f371b119fef", + "rev": "f5c5c917b38bd61e13f781daa317d18ddd28f494", "type": "github" }, "original": { @@ -704,7 +704,10 @@ }, "ndg": { "inputs": { - "nixpkgs": "nixpkgs_8" + "nixpkgs": [ + "nvf", + "nixpkgs" + ] }, "locked": { "lastModified": 1765720983, @@ -728,11 +731,11 @@ ] }, "locked": { - "lastModified": 1764161084, - "narHash": "sha256-HN84sByg9FhJnojkGGDSrcjcbeioFWoNXfuyYfJ1kBE=", + "lastModified": 1767028240, + "narHash": "sha256-0/fLUqwJ4Z774muguUyn5t8AQ6wyxlNbHexpje+5hRo=", "owner": "nix-darwin", "repo": "nix-darwin", - "rev": "e95de00a471d07435e0527ff4db092c84998698e", + "rev": "c31afa6e76da9bbc7c9295e39c7de9fca1071ea1", "type": "github" }, "original": { @@ -770,11 +773,11 @@ "nixpkgs": "nixpkgs_6" }, "locked": { - "lastModified": 1761530861, - "narHash": "sha256-VMhre9pdUAT6TDo0KV1kOjtZywCEoBowKRYSaa7KHP0=", + "lastModified": 1767060660, + "narHash": "sha256-8sqnhJcmHZ4OzLxmTtblQgpazosuhggeGZ2yeMvWOi0=", "owner": "Infinidoge", "repo": "nix-minecraft", - "rev": "c90769ae2c7d46fdadcdb81e09a97137b3b87891", + "rev": "1d3efec981bcb162a7921b29502ad369056295cb", "type": "github" }, "original": { @@ -828,11 +831,11 @@ }, "nixos-facter-modules": { "locked": { - "lastModified": 1765442039, - "narHash": "sha256-k3lYQ+A1F7aTz8HnlU++bd9t/x/NP2A4v9+x6opcVg0=", + "lastModified": 1766558141, + "narHash": "sha256-Ud9v49ZPsoDBFuyJSQ2Mpw1ZgAH/aMwUwwzrVoetNus=", "owner": "nix-community", "repo": "nixos-facter-modules", - "rev": "9dd775ee92de63f14edd021d59416e18ac2c00f1", + "rev": "e796d536e3d83de74267069e179dc620a608ed7d", "type": "github" }, "original": { @@ -870,11 +873,11 @@ ] }, "locked": { - "lastModified": 1761563673, - "narHash": "sha256-d+1TpVAmRjcNBfjZsh2yQSdwUfN7Xgz1blJ185g73+A=", + "lastModified": 1765841014, + "narHash": "sha256-55V0AJ36V5Egh4kMhWtDh117eE3GOjwq5LhwxDn9eHg=", "owner": "nix-community", "repo": "nixos-wsl", - "rev": "a518cf710e5ebb935518dc7ac98e07e7ee5014c3", + "rev": "be4af8042e7a61fa12fda58fe9a3b3babdefe17b", "type": "github" }, "original": { @@ -901,11 +904,11 @@ }, "nixpkgs-lib": { "locked": { - "lastModified": 1765674936, - "narHash": "sha256-k00uTP4JNfmejrCLJOwdObYC9jHRrr/5M/a/8L2EIdo=", + "lastModified": 1766884708, + "narHash": "sha256-x8nyRwtD0HMeYtX60xuIuZJbwwoI7/UKAdCiATnQNz0=", "owner": "nix-community", "repo": "nixpkgs.lib", - "rev": "2075416fcb47225d9b68ac469a5c4801a9c4dd85", + "rev": "15177f81ad356040b4460a676838154cbf7f6213", "type": "github" }, "original": { @@ -915,22 +918,6 @@ } }, "nixpkgs_10": { - "locked": { - "lastModified": 1765457389, - "narHash": "sha256-ddhDtNYvleZeYF7g7TRFSmuQuZh7HCgqstg5YBGwo5s=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "f997fa0f94fb1ce55bccb97f60d41412ae8fde4c", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_11": { "locked": { "lastModified": 1764517877, "narHash": "sha256-pp3uT4hHijIC8JUK5MEqeAWmParJrgBVzHLNfJDZxg4=", @@ -948,11 +935,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1761544814, - "narHash": "sha256-t5f0A+2MtSWTfA6hzMNiotpIMGLlSQF2JnK9m6nkzIY=", + "lastModified": 1767116409, + "narHash": "sha256-5vKw92l1GyTnjoLzEagJy5V5mDFck72LiQWZSOnSicw=", "owner": "nixos", "repo": "nixpkgs", - "rev": "e5aa45ed6c45058ec109658b2b7352a9a062cdf3", + "rev": "cad22e7d996aea55ecab064e84834289143e44a0", "type": "github" }, "original": { @@ -980,11 +967,11 @@ }, "nixpkgs_4": { "locked": { - "lastModified": 1761595987, - "narHash": "sha256-rIUh2oI9qfBmusxTlsqhfAdDU6IAl8OcDAu5OXNyeLc=", + "lastModified": 1767143992, + "narHash": "sha256-c3jlq36uxltxGLuQ3KPYfxZkue/LLD0Ct3NdhBUsRyo=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ca1d8cb9a4bd42196e73f341d66d0805bce8b1c8", + "rev": "5830d8dfe6ae79365987d78bda3dd4152c271d8b", "type": "github" }, "original": { @@ -1028,11 +1015,11 @@ }, "nixpkgs_7": { "locked": { - "lastModified": 1761373498, - "narHash": "sha256-Q/uhWNvd7V7k1H1ZPMy/vkx3F8C13ZcdrKjO7Jv7v0c=", + "lastModified": 1766902085, + "narHash": "sha256-coBu0ONtFzlwwVBzmjacUQwj3G+lybcZ1oeNSQkgC0M=", "owner": "nixos", "repo": "nixpkgs", - "rev": "6a08e6bb4e46ff7fcbb53d409b253f6bad8a28ce", + "rev": "c0b0e0fddf73fd517c3471e546c0df87a42d53f4", "type": "github" }, "original": { @@ -1043,22 +1030,6 @@ } }, "nixpkgs_8": { - "locked": { - "lastModified": 1760596604, - "narHash": "sha256-J/i5K6AAz/y5dBePHQOuzC7MbhyTOKsd/GLezSbEFiM=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "3cbe716e2346710d6e1f7c559363d14e11c32a43", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_9": { "locked": { "lastModified": 1764081664, "narHash": "sha256-sUoHmPr/EwXzRMpv1u/kH+dXuvJEyyF2Q7muE+t0EU4=", @@ -1074,6 +1045,22 @@ "type": "github" } }, + "nixpkgs_9": { + "locked": { + "lastModified": 1766840161, + "narHash": "sha256-Ss/LHpJJsng8vz1Pe33RSGIWUOcqM1fjrehjUkdrWio=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "3edc4a30ed3903fdf6f90c837f961fa6b49582d1", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, "nur": { "inputs": { "flake-parts": [ @@ -1105,15 +1092,15 @@ "flake-parts": "flake-parts_3", "mnw": "mnw", "ndg": "ndg", - "nixpkgs": "nixpkgs_9", + "nixpkgs": "nixpkgs_8", "systems": "systems_5" }, "locked": { - "lastModified": 1761486540, - "narHash": "sha256-O0VNqERaZ1H+4P3XwHNd8wVCqUcGtMPJT1z4cJodRFc=", + "lastModified": 1767123832, + "narHash": "sha256-WI+DaMQLJ/QVUKCNk1gvo8y0Rw6C4uDx8BW1mRVVOMU=", "owner": "notashelf", "repo": "nvf", - "rev": "4b904de36157035fa3dddf0312a4242e5d0d9bd0", + "rev": "0390abd6736ff34a016afc66366d1f46372f28de", "type": "github" }, "original": { @@ -1132,11 +1119,11 @@ ] }, "locked": { - "lastModified": 1761078382, - "narHash": "sha256-JNJesbe9MMN1Brq41BHEpuH+Z+Zg74y/nI5AFZX84Vw=", + "lastModified": 1763909441, + "narHash": "sha256-56LwV51TX/FhgX+5LCG6akQ5KrOWuKgcJa+eUsRMxsc=", "owner": "nix-community", "repo": "plasma-manager", - "rev": "27dfa61b64d0cdb8e4ba6f3aaa4d4e067d64cb5c", + "rev": "b24ed4b272256dfc1cc2291f89a9821d5f9e14b4", "type": "github" }, "original": { @@ -1174,11 +1161,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1761500479, - "narHash": "sha256-syeBTCCU96qPJHcVpwHeCwmPCiLTDHHgYQYhpZ0iwLo=", + "lastModified": 1767028829, + "narHash": "sha256-RZ5+NUYTkAZ0rtbR3xBxs3VX0yi0IgYfjL06NBbXipk=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "049767e6faa84b2d1a951d8f227e6ebd99d728a2", + "rev": "d7117056a36c0f42a4deea1ac4a8e297db11cbb8", "type": "github" }, "original": { @@ -1196,11 +1183,11 @@ ] }, "locked": { - "lastModified": 1761532837, - "narHash": "sha256-78mCSQgC/a6/0vWYrvE/g9E3gGsJLyBBGtmHe3ZOLG4=", + "lastModified": 1759977258, + "narHash": "sha256-hOxEFSEBoqDmJb7BGX1CzT1gvUPK6r+Qs+n3IxBgfTs=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "4f5f89f1cfd8553b1285a4a0879ea1b2b05ad286", + "rev": "1d0c6173f57d07db7957b50e799240d4f2d7520f", "type": "github" }, "original": { @@ -1239,11 +1226,29 @@ ] }, "locked": { - "lastModified": 1760998189, - "narHash": "sha256-ee2e1/AeGL5X8oy/HXsZQvZnae6XfEVdstGopKucYLY=", + "lastModified": 1766894905, + "narHash": "sha256-pn8AxxfajqyR/Dmr1wnZYdUXHgM3u6z9x0Z1Ijmz2UQ=", "owner": "Mic92", "repo": "sops-nix", - "rev": "5a7d18b5c55642df5c432aadb757140edfeb70b3", + "rev": "61b39c7b657081c2adc91b75dd3ad8a91d6f07a7", + "type": "github" + }, + "original": { + "owner": "Mic92", + "repo": "sops-nix", + "type": "github" + } + }, + "sops-nix_2": { + "inputs": { + "nixpkgs": "nixpkgs_9" + }, + "locked": { + "lastModified": 1766894905, + "narHash": "sha256-pn8AxxfajqyR/Dmr1wnZYdUXHgM3u6z9x0Z1Ijmz2UQ=", + "owner": "Mic92", + "repo": "sops-nix", + "rev": "61b39c7b657081c2adc91b75dd3ad8a91d6f07a7", "type": "github" }, "original": { @@ -1261,7 +1266,7 @@ "firefox-gnome-theme": "firefox-gnome-theme", "flake-parts": "flake-parts_4", "gnome-shell": "gnome-shell", - "nixpkgs": "nixpkgs_11", + "nixpkgs": "nixpkgs_10", "nur": "nur", "systems": "systems_7", "tinted-foot": "tinted-foot", @@ -1271,11 +1276,11 @@ "tinted-zed": "tinted-zed" }, "locked": { - "lastModified": 1761028816, - "narHash": "sha256-s1XiIeJHpODVWfzsPaK9e21iz1dQSCU3H4/1OxOsyps=", + "lastModified": 1766603026, + "narHash": "sha256-J2DDdRqSU4w9NNgkMfmMeaLIof5PXtS9RG7y6ckDvQE=", "owner": "nix-community", "repo": "stylix", - "rev": "b81dc0a385443099e7d231fe6275189e32c3b760", + "rev": "551df12ee3ebac52c5712058bd97fd9faa4c3430", "type": "github" }, "original": { @@ -1515,11 +1520,11 @@ ] }, "locked": { - "lastModified": 1766000401, - "narHash": "sha256-+cqN4PJz9y0JQXfAK5J1drd0U05D5fcAGhzhfVrDlsI=", + "lastModified": 1767122417, + "narHash": "sha256-yOt/FTB7oSEKQH9EZMFMeuldK1HGpQs2eAzdS9hNS/o=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "42d96e75aa56a3f70cab7e7dc4a32868db28e8fd", + "rev": "dec15f37015ac2e774c84d0952d57fcdf169b54d", "type": "github" }, "original": { @@ -1536,11 +1541,11 @@ ] }, "locked": { - "lastModified": 1761535208, - "narHash": "sha256-E1PobJMiFmVUX2YdqYk/MpKb0LXavOYvlg8DCBBzlHc=", + "lastModified": 1767119591, + "narHash": "sha256-4LqJZvu+8i0cTtwz+N3nfIvVf6Ra4xIGw0UxOOHVKAc=", "owner": "0xc000022070", "repo": "zen-browser-flake", - "rev": "79a94872a3e6993a051c4e22a2dcb02c1d088acf", + "rev": "379639ecac155c03975cd6608a146bb1dc168cf9", "type": "github" }, "original": { From baa67ad6cd2ea3f6af4911491ad23f1fd5bd3443 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Wed, 31 Dec 2025 02:40:44 +0100 Subject: [PATCH 043/108] . --- modules/nixos/application/steam/default.nix | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/nixos/application/steam/default.nix b/modules/nixos/application/steam/default.nix index 735aa80..5b08d31 100644 --- a/modules/nixos/application/steam/default.nix +++ b/modules/nixos/application/steam/default.nix @@ -25,18 +25,18 @@ in { }; }; - gamescopeSession = { - enable = true; - args = ["--immediate-flips"]; - }; + # gamescopeSession = { + # enable = true; + # args = ["--immediate-flips"]; + # }; }; # https://github.com/FeralInteractive/gamemode - gamemode = { - enable = true; - enableRenice = true; - settings = {}; - }; + # gamemode = { + # enable = true; + # enableRenice = true; + # settings = {}; + # }; # gamescope = { # enable = true; From 58236bbddda8da1882775cf04eb77af24273cccd Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Fri, 2 Jan 2026 23:33:36 +0100 Subject: [PATCH 044/108] chore: update dependencies --- flake.lock | 282 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 166 insertions(+), 116 deletions(-) diff --git a/flake.lock b/flake.lock index 0aebd9e..78d9c06 100644 --- a/flake.lock +++ b/flake.lock @@ -84,17 +84,32 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1767139704, - "narHash": "sha256-ftRAe4dLMx+D3ArFtEJqoTsV0Y1vegFKx4YZb1/v7dU=", - "rev": "7fa4abb7b016c3a7ae3f346784fac0298a9b14fb", + "lastModified": 1767372260, + "narHash": "sha256-a7j0XI+rMyNAOqD1kysNIgJ1gdrLhrpRPC1cgSGjNOo=", + "rev": "7666e9a19ae08b356952857d02628da3c2a6fd28", "type": "tarball", - "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/7fa4abb7b016c3a7ae3f346784fac0298a9b14fb.tar.gz" + "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/7666e9a19ae08b356952857d02628da3c2a6fd28.tar.gz" }, "original": { "type": "tarball", "url": "https://git.clan.lol/clan/clan-core/archive/main.tar.gz" } }, + "crane": { + "locked": { + "lastModified": 1757183466, + "narHash": "sha256-kTdCCMuRE+/HNHES5JYsbRHmgtr+l9mOtf5dpcMppVc=", + "owner": "ipetkov", + "repo": "crane", + "rev": "d599ae4847e7f87603e7082d73ca673aa93c916d", + "type": "github" + }, + "original": { + "owner": "ipetkov", + "repo": "crane", + "type": "github" + } + }, "data-mesher": { "inputs": { "flake-parts": [ @@ -149,11 +164,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1766957541, - "narHash": "sha256-5uwnfwFgK5UMwgC0eaLIGGNuQpWOEywUiexEnbqeOAs=", + "lastModified": 1767359690, + "narHash": "sha256-PYMJPQtl9PwRFd1B9u1O0LWlPY/X6ShARFpIb3lLejE=", "owner": "emmanuelrosa", "repo": "erosanix", - "rev": "8c7d54b7d8879c14dfa914ece38815bf9c248f8b", + "rev": "464f070d952afff764d82041d371cfee3e689d2a", "type": "github" }, "original": { @@ -170,11 +185,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1767077117, - "narHash": "sha256-tmVJMQC4aNUCME3ofKP2wWEBizabxwFZfLpZSi0S/4Q=", + "lastModified": 1767336565, + "narHash": "sha256-19hSId4vCMqf9dA++q+LOfpbncg1Fp5HNhcV5MQLdsw=", "owner": "nix-community", "repo": "fenix", - "rev": "f69c299a340f95776ddcfecfc0b1f6183c0c298e", + "rev": "dab1b0c7b3cdd7aa27882ff9ecb740a00cf3a174", "type": "github" }, "original": { @@ -190,11 +205,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1767133124, - "narHash": "sha256-CQPz7FIUZsIX+6uJFBWMvm3VlwjRDunwgXYghHa/o+0=", + "lastModified": 1767352676, + "narHash": "sha256-uUXS2pN+Qa6bGzcKCZ/oU1sxZgnoZc52zaKplYsydEw=", "owner": "nix-community", "repo": "flake-firefox-nightly", - "rev": "a99b92ac7003944b11e5a3ee0a6db65383d9ed58", + "rev": "b34a30373fa8cedf7d9199f6bd37a90d9ed8210e", "type": "github" }, "original": { @@ -496,14 +511,15 @@ }, "flux": { "inputs": { - "nixpkgs": "nixpkgs_3" + "mcman": "mcman", + "nixpkgs": "nixpkgs_4" }, "locked": { - "lastModified": 1729388191, - "narHash": "sha256-Ga5JJPgwfpqMFvk8g4un7ySwfTefGCsbUrCDsSnQyiU=", + "lastModified": 1767316901, + "narHash": "sha256-tllrks9CW9WWWa0w9Brc25uieYixR8pAMUJODith/yU=", "owner": "IogaMaster", "repo": "flux", - "rev": "400896b5c977e0569ea0f8bacb9b42509e0bbd00", + "rev": "b12173e0b75c10c8f590a9d3a3ad23681f14d038", "type": "github" }, "original": { @@ -549,7 +565,7 @@ }, "grub2-themes": { "inputs": { - "nixpkgs": "nixpkgs_4" + "nixpkgs": "nixpkgs_5" }, "locked": { "lastModified": 1757136219, @@ -594,11 +610,11 @@ ] }, "locked": { - "lastModified": 1767104570, - "narHash": "sha256-GKgwu5//R+cLdKysZjGqvUEEOGXXLdt93sNXeb2M/Lk=", + "lastModified": 1767391542, + "narHash": "sha256-qHXxJuFkQhggyeao/kQb6KcOjgz0Ky+ArfowRX1MHaE=", "owner": "nix-community", "repo": "home-manager", - "rev": "e4e78a2cbeaddd07ab7238971b16468cc1d14daf", + "rev": "2f06b726061b7e1aa69f718e943da9ffcecd6397", "type": "github" }, "original": { @@ -668,6 +684,25 @@ "type": "github" } }, + "mcman": { + "inputs": { + "crane": "crane", + "nixpkgs": "nixpkgs_3" + }, + "locked": { + "lastModified": 1766962671, + "narHash": "sha256-n1+76Xuk30JKy4raeU5okeaYZjzDQkS+bBLjX8NGZIg=", + "owner": "deniz-blue", + "repo": "mcman", + "rev": "0e3bef55234406eb0634b80f9cac9a284254768a", + "type": "github" + }, + "original": { + "owner": "deniz-blue", + "repo": "mcman", + "type": "github" + } + }, "mnw": { "locked": { "lastModified": 1758834834, @@ -686,7 +721,7 @@ "mydia": { "inputs": { "flake-parts": "flake-parts_2", - "nixpkgs": "nixpkgs_5" + "nixpkgs": "nixpkgs_6" }, "locked": { "lastModified": 1764866402, @@ -770,14 +805,14 @@ "inputs": { "flake-compat": "flake-compat_3", "flake-utils": "flake-utils_3", - "nixpkgs": "nixpkgs_6" + "nixpkgs": "nixpkgs_7" }, "locked": { - "lastModified": 1767060660, - "narHash": "sha256-8sqnhJcmHZ4OzLxmTtblQgpazosuhggeGZ2yeMvWOi0=", + "lastModified": 1767147099, + "narHash": "sha256-395ehjdAtaqCbKmx+PhKAqnkYLvTtAzq2qzFG9qaGDw=", "owner": "Infinidoge", "repo": "nix-minecraft", - "rev": "1d3efec981bcb162a7921b29502ad369056295cb", + "rev": "01f571579edd64433f97c4294137fbc366deef4b", "type": "github" }, "original": { @@ -888,11 +923,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1764255304, - "narHash": "sha256-oQPux8afXmkbb88ceRtz1lgSGqL9auOgdYnBSqpVgSA=", + "lastModified": 1767276799, + "narHash": "sha256-FW1bq43LRGMwA+SNoe64fMsM19/5QS+8rwfECmiukxk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "6e86c955fc372d12face4a9c0d932a6e0f7bff4d", + "rev": "af559d367a9ff45e6e0f8c5b214d12dfa6ac4155", "type": "github" }, "original": { @@ -918,6 +953,22 @@ } }, "nixpkgs_10": { + "locked": { + "lastModified": 1766840161, + "narHash": "sha256-Ss/LHpJJsng8vz1Pe33RSGIWUOcqM1fjrehjUkdrWio=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "3edc4a30ed3903fdf6f90c837f961fa6b49582d1", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_11": { "locked": { "lastModified": 1764517877, "narHash": "sha256-pp3uT4hHijIC8JUK5MEqeAWmParJrgBVzHLNfJDZxg4=", @@ -935,11 +986,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1767116409, - "narHash": "sha256-5vKw92l1GyTnjoLzEagJy5V5mDFck72LiQWZSOnSicw=", + "lastModified": 1767272170, + "narHash": "sha256-zF+04fmLe8TfKB0dK5gOIP1rv7rdbtEy5X4/6/ILGTw=", "owner": "nixos", "repo": "nixpkgs", - "rev": "cad22e7d996aea55ecab064e84834289143e44a0", + "rev": "75eeaa9145450f8e71ba554a3743978007d5d5f4", "type": "github" }, "original": { @@ -951,69 +1002,20 @@ }, "nixpkgs_3": { "locked": { - "lastModified": 1712608508, - "narHash": "sha256-vMZ5603yU0wxgyQeHJryOI+O61yrX2AHwY6LOFyV1gM=", - "owner": "nixos", + "lastModified": 1757347588, + "narHash": "sha256-tLdkkC6XnsY9EOZW9TlpesTclELy8W7lL2ClL+nma8o=", + "owner": "NixOS", "repo": "nixpkgs", - "rev": "4cba8b53da471aea2ab2b0c1f30a81e7c451f4b6", + "rev": "b599843bad24621dcaa5ab60dac98f9b0eb1cabe", "type": "github" }, "original": { - "owner": "nixos", + "id": "nixpkgs", "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" + "type": "indirect" } }, "nixpkgs_4": { - "locked": { - "lastModified": 1767143992, - "narHash": "sha256-c3jlq36uxltxGLuQ3KPYfxZkue/LLD0Ct3NdhBUsRyo=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "5830d8dfe6ae79365987d78bda3dd4152c271d8b", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "master", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_5": { - "locked": { - "lastModified": 1764242076, - "narHash": "sha256-sKoIWfnijJ0+9e4wRvIgm/HgE27bzwQxcEmo2J/gNpI=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "2fad6eac6077f03fe109c4d4eb171cf96791faa4", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_6": { - "locked": { - "lastModified": 1748929857, - "narHash": "sha256-lcZQ8RhsmhsK8u7LIFsJhsLh/pzR9yZ8yqpTzyGdj+Q=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "c2a03962b8e24e669fb37b7df10e7c79531ff1a4", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_7": { "locked": { "lastModified": 1766902085, "narHash": "sha256-coBu0ONtFzlwwVBzmjacUQwj3G+lybcZ1oeNSQkgC0M=", @@ -1029,7 +1031,71 @@ "type": "github" } }, + "nixpkgs_5": { + "locked": { + "lastModified": 1767392736, + "narHash": "sha256-dHTkXf8lzbU3ccfWAEUJ3tU5s6jQHR6J5Fijzqfo7D0=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "c816304350cb914bf8b1057d3f78109aa5b9be1b", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "master", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_6": { + "locked": { + "lastModified": 1764242076, + "narHash": "sha256-sKoIWfnijJ0+9e4wRvIgm/HgE27bzwQxcEmo2J/gNpI=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "2fad6eac6077f03fe109c4d4eb171cf96791faa4", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_7": { + "locked": { + "lastModified": 1748929857, + "narHash": "sha256-lcZQ8RhsmhsK8u7LIFsJhsLh/pzR9yZ8yqpTzyGdj+Q=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "c2a03962b8e24e669fb37b7df10e7c79531ff1a4", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, "nixpkgs_8": { + "locked": { + "lastModified": 1767116409, + "narHash": "sha256-5vKw92l1GyTnjoLzEagJy5V5mDFck72LiQWZSOnSicw=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "cad22e7d996aea55ecab064e84834289143e44a0", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_9": { "locked": { "lastModified": 1764081664, "narHash": "sha256-sUoHmPr/EwXzRMpv1u/kH+dXuvJEyyF2Q7muE+t0EU4=", @@ -1045,22 +1111,6 @@ "type": "github" } }, - "nixpkgs_9": { - "locked": { - "lastModified": 1766840161, - "narHash": "sha256-Ss/LHpJJsng8vz1Pe33RSGIWUOcqM1fjrehjUkdrWio=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "3edc4a30ed3903fdf6f90c837f961fa6b49582d1", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, "nur": { "inputs": { "flake-parts": [ @@ -1092,15 +1142,15 @@ "flake-parts": "flake-parts_3", "mnw": "mnw", "ndg": "ndg", - "nixpkgs": "nixpkgs_8", + "nixpkgs": "nixpkgs_9", "systems": "systems_5" }, "locked": { - "lastModified": 1767123832, - "narHash": "sha256-WI+DaMQLJ/QVUKCNk1gvo8y0Rw6C4uDx8BW1mRVVOMU=", + "lastModified": 1767369300, + "narHash": "sha256-QV+tdP2bS+PJBcp4YHhqpMTzcxsxGaS/d6cKMCJ4PnA=", "owner": "notashelf", "repo": "nvf", - "rev": "0390abd6736ff34a016afc66366d1f46372f28de", + "rev": "9c75c2a199af39fc95fb203636ce97d070ca3973", "type": "github" }, "original": { @@ -1148,7 +1198,7 @@ "nixos-boot": "nixos-boot", "nixos-generators": "nixos-generators", "nixos-wsl": "nixos-wsl", - "nixpkgs": "nixpkgs_7", + "nixpkgs": "nixpkgs_8", "nvf": "nvf", "plasma-manager": "plasma-manager", "snowfall-lib": "snowfall-lib", @@ -1161,11 +1211,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1767028829, - "narHash": "sha256-RZ5+NUYTkAZ0rtbR3xBxs3VX0yi0IgYfjL06NBbXipk=", + "lastModified": 1767191410, + "narHash": "sha256-cCZGjubgDWmstvFkS6eAw2qk2ihgWkycw55u2dtLd70=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "d7117056a36c0f42a4deea1ac4a8e297db11cbb8", + "rev": "a9026e6d5068172bf5a0d52a260bb290961d1cb4", "type": "github" }, "original": { @@ -1241,7 +1291,7 @@ }, "sops-nix_2": { "inputs": { - "nixpkgs": "nixpkgs_9" + "nixpkgs": "nixpkgs_10" }, "locked": { "lastModified": 1766894905, @@ -1266,7 +1316,7 @@ "firefox-gnome-theme": "firefox-gnome-theme", "flake-parts": "flake-parts_4", "gnome-shell": "gnome-shell", - "nixpkgs": "nixpkgs_10", + "nixpkgs": "nixpkgs_11", "nur": "nur", "systems": "systems_7", "tinted-foot": "tinted-foot", @@ -1541,11 +1591,11 @@ ] }, "locked": { - "lastModified": 1767119591, - "narHash": "sha256-4LqJZvu+8i0cTtwz+N3nfIvVf6Ra4xIGw0UxOOHVKAc=", + "lastModified": 1767381285, + "narHash": "sha256-tdqw7/oQLdJbQqYUA+yGNCHuOjWCTYB1J1U8BAJwHGo=", "owner": "0xc000022070", "repo": "zen-browser-flake", - "rev": "379639ecac155c03975cd6608a146bb1dc168cf9", + "rev": "92463488fe6ea67b0672345e16c4259e9edcc2ac", "type": "github" }, "original": { From 45629f2a6f49b4ce05ac3850505a71e527e15b1f Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Thu, 8 Jan 2026 16:59:40 +0100 Subject: [PATCH 045/108] chore: update dependencies --- flake.lock | 214 ++++++++++++++++++++++++++--------------------------- 1 file changed, 107 insertions(+), 107 deletions(-) diff --git a/flake.lock b/flake.lock index 78d9c06..573cd43 100644 --- a/flake.lock +++ b/flake.lock @@ -84,11 +84,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1767372260, - "narHash": "sha256-a7j0XI+rMyNAOqD1kysNIgJ1gdrLhrpRPC1cgSGjNOo=", - "rev": "7666e9a19ae08b356952857d02628da3c2a6fd28", + "lastModified": 1767876003, + "narHash": "sha256-ZXotZl4Ipylax0BLUlUHtpw+Lm2qHVTmbX8LuZI8He4=", + "rev": "9ac4c3ceafdc9f50eab90f70fecc182e38169622", "type": "tarball", - "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/7666e9a19ae08b356952857d02628da3c2a6fd28.tar.gz" + "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/9ac4c3ceafdc9f50eab90f70fecc182e38169622.tar.gz" }, "original": { "type": "tarball", @@ -126,11 +126,11 @@ ] }, "locked": { - "lastModified": 1766977667, - "narHash": "sha256-LUALgG4ZpsA0k7pGYzMDto/r6T8aIPlYTok3lGlojjA=", - "rev": "3f852546b5d8bd2e9659a81c6b2cc14922e63a94", + "lastModified": 1767582502, + "narHash": "sha256-WVcYGWcAlWzVt38OaTC5i5Q3QkIKJKZsJ7LcQZVVxeE=", + "rev": "31f2e3ecf207fd2760e3cebf7c2cf3cb7170ea3d", "type": "tarball", - "url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/3f852546b5d8bd2e9659a81c6b2cc14922e63a94.tar.gz" + "url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/31f2e3ecf207fd2760e3cebf7c2cf3cb7170ea3d.tar.gz" }, "original": { "type": "tarball", @@ -164,11 +164,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1767359690, - "narHash": "sha256-PYMJPQtl9PwRFd1B9u1O0LWlPY/X6ShARFpIb3lLejE=", + "lastModified": 1767731360, + "narHash": "sha256-L8PmjsBDqWt9gBCp+0wSW6WXA9iVylceltk98HFRP4Y=", "owner": "emmanuelrosa", "repo": "erosanix", - "rev": "464f070d952afff764d82041d371cfee3e689d2a", + "rev": "4a43987a3b8efa816534d82c696ad48f1152f457", "type": "github" }, "original": { @@ -185,11 +185,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1767336565, - "narHash": "sha256-19hSId4vCMqf9dA++q+LOfpbncg1Fp5HNhcV5MQLdsw=", + "lastModified": 1767854969, + "narHash": "sha256-kJQ920Ymo4rrgFlSdNw/ZYpdd1uWL5Ik1VKpZaKsQ0c=", "owner": "nix-community", "repo": "fenix", - "rev": "dab1b0c7b3cdd7aa27882ff9ecb740a00cf3a174", + "rev": "1ad578f9643053f87843215b1786fd8e66ddb384", "type": "github" }, "original": { @@ -205,11 +205,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1767352676, - "narHash": "sha256-uUXS2pN+Qa6bGzcKCZ/oU1sxZgnoZc52zaKplYsydEw=", + "lastModified": 1767810592, + "narHash": "sha256-bBuww/tHGv4N5tXFbOkWen4KKF9TfdR4xnXimTP+WVI=", "owner": "nix-community", "repo": "flake-firefox-nightly", - "rev": "b34a30373fa8cedf7d9199f6bd37a90d9ed8210e", + "rev": "1a411ec44c27e123f7136027276bb56e1b76861b", "type": "github" }, "original": { @@ -221,11 +221,11 @@ "firefox-gnome-theme": { "flake": false, "locked": { - "lastModified": 1764724327, - "narHash": "sha256-OkFLrD3pFR952TrjQi1+Vdj604KLcMnkpa7lkW7XskI=", + "lastModified": 1764873433, + "narHash": "sha256-1XPewtGMi+9wN9Ispoluxunw/RwozuTRVuuQOmxzt+A=", "owner": "rafaelmardojai", "repo": "firefox-gnome-theme", - "rev": "66b7c635763d8e6eb86bd766de5a1e1fbfcc1047", + "rev": "f7ffd917ac0d253dbd6a3bf3da06888f57c69f92", "type": "github" }, "original": { @@ -321,11 +321,11 @@ ] }, "locked": { - "lastModified": 1765835352, - "narHash": "sha256-XswHlK/Qtjasvhd1nOa1e8MgZ8GS//jBoTqWtrS1Giw=", + "lastModified": 1767609335, + "narHash": "sha256-feveD98mQpptwrAEggBQKJTYbvwwglSbOv53uCfH9PY=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "a34fae9c08a15ad73f295041fec82323541400a9", + "rev": "250481aafeb741edfe23d29195671c19b36b6dca", "type": "github" }, "original": { @@ -384,11 +384,11 @@ ] }, "locked": { - "lastModified": 1763759067, - "narHash": "sha256-LlLt2Jo/gMNYAwOgdRQBrsRoOz7BPRkzvNaI/fzXi2Q=", + "lastModified": 1767609335, + "narHash": "sha256-feveD98mQpptwrAEggBQKJTYbvwwglSbOv53uCfH9PY=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "2cccadc7357c0ba201788ae99c4dfa90728ef5e0", + "rev": "250481aafeb741edfe23d29195671c19b36b6dca", "type": "github" }, "original": { @@ -548,11 +548,11 @@ "flake": false, "locked": { "host": "gitlab.gnome.org", - "lastModified": 1764524476, - "narHash": "sha256-bTmNn3Q4tMQ0J/P0O5BfTQwqEnCiQIzOGef9/aqAZvk=", + "lastModified": 1767737596, + "narHash": "sha256-eFujfIUQDgWnSJBablOuG+32hCai192yRdrNHTv0a+s=", "owner": "GNOME", "repo": "gnome-shell", - "rev": "c0e1ad9f0f703fd0519033b8f46c3267aab51a22", + "rev": "ef02db02bf0ff342734d525b5767814770d85b49", "type": "gitlab" }, "original": { @@ -590,11 +590,11 @@ "rust-overlay": "rust-overlay" }, "locked": { - "lastModified": 1766174774, - "narHash": "sha256-kLeXugTZLamNMh8oPg/nnkLQGH9XXgncTbx/0R7X+w4=", + "lastModified": 1767649346, + "narHash": "sha256-hzu/Z8yhR3sRU4RFGwIrVkX+ovxrWtT7nMWLOvUwHEc=", "owner": "himmelblau-idm", "repo": "himmelblau", - "rev": "fc297543cef873c5c4f4a8a8f0aa7d745361e63a", + "rev": "df0bee9fdd170a92ff99b9526510f030d29b6d4a", "type": "github" }, "original": { @@ -610,11 +610,11 @@ ] }, "locked": { - "lastModified": 1767391542, - "narHash": "sha256-qHXxJuFkQhggyeao/kQb6KcOjgz0Ky+ArfowRX1MHaE=", + "lastModified": 1767878997, + "narHash": "sha256-pOWcb+hKeRM40eo6+aqTVcGPS1pCPoMOJHvP3So7VBM=", "owner": "nix-community", "repo": "home-manager", - "rev": "2f06b726061b7e1aa69f718e943da9ffcecd6397", + "rev": "d1da1de5c27bcedfbf8d8f260b63b51ee5442550", "type": "github" }, "original": { @@ -631,11 +631,11 @@ ] }, "locked": { - "lastModified": 1765682243, - "narHash": "sha256-yeCxFV/905Wr91yKt5zrVvK6O2CVXWRMSrxqlAZnLp0=", + "lastModified": 1767104570, + "narHash": "sha256-GKgwu5//R+cLdKysZjGqvUEEOGXXLdt93sNXeb2M/Lk=", "owner": "nix-community", "repo": "home-manager", - "rev": "58bf3ecb2d0bba7bdf363fc8a6c4d49b4d509d03", + "rev": "e4e78a2cbeaddd07ab7238971b16468cc1d14daf", "type": "github" }, "original": { @@ -652,11 +652,11 @@ ] }, "locked": { - "lastModified": 1767082077, - "narHash": "sha256-2tL1mRb9uFJThUNfuDm/ehrnPvImL/QDtCxfn71IEz4=", + "lastModified": 1767777502, + "narHash": "sha256-jXb2kBU6lO6Q6S9zoR/bhVLMjg2hM9EW8gWIwsmkj64=", "owner": "Jovian-Experiments", "repo": "Jovian-NixOS", - "rev": "efd4b22e6fdc6d7fb4e186ae333a4b74e03da440", + "rev": "a81fad3f4a70fdaa779e74b7da2063fa2e358028", "type": "github" }, "original": { @@ -671,11 +671,11 @@ "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1766926104, - "narHash": "sha256-c5CozmannX3I/ax0Ig9z/QjXRuNY/j3XTOx6KXvMUs4=", + "lastModified": 1767530944, + "narHash": "sha256-DPczJq4hmhqG9i7wo+zARji+hUj1iKR3Qnz8BTuFgHU=", "owner": "nix-community", "repo": "lib-aggregate", - "rev": "f5c5c917b38bd61e13f781daa317d18ddd28f494", + "rev": "f32c12294f38b793121079bcf98fcb30d5440edc", "type": "github" }, "original": { @@ -766,11 +766,11 @@ ] }, "locked": { - "lastModified": 1767028240, - "narHash": "sha256-0/fLUqwJ4Z774muguUyn5t8AQ6wyxlNbHexpje+5hRo=", + "lastModified": 1767718503, + "narHash": "sha256-V+VkFs0aSG0ca8p/N3gib7FAf4cq9jyr5Gm+ZBrHQpo=", "owner": "nix-darwin", "repo": "nix-darwin", - "rev": "c31afa6e76da9bbc7c9295e39c7de9fca1071ea1", + "rev": "9f48ffaca1f44b3e590976b4da8666a9e86e6eb1", "type": "github" }, "original": { @@ -808,11 +808,11 @@ "nixpkgs": "nixpkgs_7" }, "locked": { - "lastModified": 1767147099, - "narHash": "sha256-395ehjdAtaqCbKmx+PhKAqnkYLvTtAzq2qzFG9qaGDw=", + "lastModified": 1767838769, + "narHash": "sha256-KCLU6SUU80tEBKIVZsBrSjRYX6kn1eVIYI3fEEqOp24=", "owner": "Infinidoge", "repo": "nix-minecraft", - "rev": "01f571579edd64433f97c4294137fbc366deef4b", + "rev": "4da21f019f6443f513f16af7f220ba4db1cdfc04", "type": "github" }, "original": { @@ -939,11 +939,11 @@ }, "nixpkgs-lib": { "locked": { - "lastModified": 1766884708, - "narHash": "sha256-x8nyRwtD0HMeYtX60xuIuZJbwwoI7/UKAdCiATnQNz0=", + "lastModified": 1767489562, + "narHash": "sha256-vyCgu90Lv0poRd3Ne2rQDUj0Jfj+Oilgtj3M0CoL2SM=", "owner": "nix-community", "repo": "nixpkgs.lib", - "rev": "15177f81ad356040b4460a676838154cbf7f6213", + "rev": "c0acf4617d3542ad4c6be461c1a9b9059c937060", "type": "github" }, "original": { @@ -954,11 +954,11 @@ }, "nixpkgs_10": { "locked": { - "lastModified": 1766840161, - "narHash": "sha256-Ss/LHpJJsng8vz1Pe33RSGIWUOcqM1fjrehjUkdrWio=", + "lastModified": 1767364772, + "narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "3edc4a30ed3903fdf6f90c837f961fa6b49582d1", + "rev": "16c7794d0a28b5a37904d55bcca36003b9109aaa", "type": "github" }, "original": { @@ -970,11 +970,11 @@ }, "nixpkgs_11": { "locked": { - "lastModified": 1764517877, - "narHash": "sha256-pp3uT4hHijIC8JUK5MEqeAWmParJrgBVzHLNfJDZxg4=", + "lastModified": 1767767207, + "narHash": "sha256-Mj3d3PfwltLmukFal5i3fFt27L6NiKXdBezC1EBuZs4=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "2d293cbfa5a793b4c50d17c05ef9e385b90edf6c", + "rev": "5912c1772a44e31bf1c63c0390b90501e5026886", "type": "github" }, "original": { @@ -986,11 +986,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1767272170, - "narHash": "sha256-zF+04fmLe8TfKB0dK5gOIP1rv7rdbtEy5X4/6/ILGTw=", + "lastModified": 1767767207, + "narHash": "sha256-Mj3d3PfwltLmukFal5i3fFt27L6NiKXdBezC1EBuZs4=", "owner": "nixos", "repo": "nixpkgs", - "rev": "75eeaa9145450f8e71ba554a3743978007d5d5f4", + "rev": "5912c1772a44e31bf1c63c0390b90501e5026886", "type": "github" }, "original": { @@ -1033,11 +1033,11 @@ }, "nixpkgs_5": { "locked": { - "lastModified": 1767392736, - "narHash": "sha256-dHTkXf8lzbU3ccfWAEUJ3tU5s6jQHR6J5Fijzqfo7D0=", + "lastModified": 1767887613, + "narHash": "sha256-QlxRV82Ad8BlMBBvwMPh8JcnAsJnZmx8FlJSOPAl2KE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "c816304350cb914bf8b1057d3f78109aa5b9be1b", + "rev": "417662c695a7f10ada76b30f866ac65f92d61883", "type": "github" }, "original": { @@ -1081,11 +1081,11 @@ }, "nixpkgs_8": { "locked": { - "lastModified": 1767116409, - "narHash": "sha256-5vKw92l1GyTnjoLzEagJy5V5mDFck72LiQWZSOnSicw=", + "lastModified": 1767767207, + "narHash": "sha256-Mj3d3PfwltLmukFal5i3fFt27L6NiKXdBezC1EBuZs4=", "owner": "nixos", "repo": "nixpkgs", - "rev": "cad22e7d996aea55ecab064e84834289143e44a0", + "rev": "5912c1772a44e31bf1c63c0390b90501e5026886", "type": "github" }, "original": { @@ -1097,11 +1097,11 @@ }, "nixpkgs_9": { "locked": { - "lastModified": 1764081664, - "narHash": "sha256-sUoHmPr/EwXzRMpv1u/kH+dXuvJEyyF2Q7muE+t0EU4=", + "lastModified": 1767364772, + "narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=", "owner": "nixos", "repo": "nixpkgs", - "rev": "dc205f7b4fdb04c8b7877b43edb7b73be7730081", + "rev": "16c7794d0a28b5a37904d55bcca36003b9109aaa", "type": "github" }, "original": { @@ -1123,11 +1123,11 @@ ] }, "locked": { - "lastModified": 1764773531, - "narHash": "sha256-mCBl7MD1WZ7yCG6bR9MmpPO2VydpNkWFgnslJRIT1YU=", + "lastModified": 1767810917, + "narHash": "sha256-ZKqhk772+v/bujjhla9VABwcvz+hB2IaRyeLT6CFnT0=", "owner": "nix-community", "repo": "NUR", - "rev": "1d9616689e98beded059ad0384b9951e967a17fa", + "rev": "dead29c804adc928d3a69dfe7f9f12d0eec1f1a4", "type": "github" }, "original": { @@ -1146,11 +1146,11 @@ "systems": "systems_5" }, "locked": { - "lastModified": 1767369300, - "narHash": "sha256-QV+tdP2bS+PJBcp4YHhqpMTzcxsxGaS/d6cKMCJ4PnA=", + "lastModified": 1767847386, + "narHash": "sha256-S8lf6YtZpJQaq38GCuao+h7LnNYFVvTpI70lNevk5xM=", "owner": "notashelf", "repo": "nvf", - "rev": "9c75c2a199af39fc95fb203636ce97d070ca3973", + "rev": "317877430a36e2e449405aaea30788119791dedc", "type": "github" }, "original": { @@ -1169,11 +1169,11 @@ ] }, "locked": { - "lastModified": 1763909441, - "narHash": "sha256-56LwV51TX/FhgX+5LCG6akQ5KrOWuKgcJa+eUsRMxsc=", + "lastModified": 1767662275, + "narHash": "sha256-d5Q1GmQ+sW1Bt8cgDE0vOihzLaswsm8cSdg8124EqXE=", "owner": "nix-community", "repo": "plasma-manager", - "rev": "b24ed4b272256dfc1cc2291f89a9821d5f9e14b4", + "rev": "51816be33a1ff0d4b22427de83222d5bfa96d30e", "type": "github" }, "original": { @@ -1211,11 +1211,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1767191410, - "narHash": "sha256-cCZGjubgDWmstvFkS6eAw2qk2ihgWkycw55u2dtLd70=", + "lastModified": 1767830331, + "narHash": "sha256-9XF16XbXvXK3UOLFZpKnFOvvQrxD8J4Lbn+brzL1rG0=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "a9026e6d5068172bf5a0d52a260bb290961d1cb4", + "rev": "54f5eea26321c33a4a4acc826b9a393cb874f85d", "type": "github" }, "original": { @@ -1276,11 +1276,11 @@ ] }, "locked": { - "lastModified": 1766894905, - "narHash": "sha256-pn8AxxfajqyR/Dmr1wnZYdUXHgM3u6z9x0Z1Ijmz2UQ=", + "lastModified": 1767826491, + "narHash": "sha256-WSBENPotD2MIhZwolL6GC9npqgaS5fkM7j07V2i/Ur8=", "owner": "Mic92", "repo": "sops-nix", - "rev": "61b39c7b657081c2adc91b75dd3ad8a91d6f07a7", + "rev": "ea3adcb6d2a000d9a69d0e23cad1f2cacb3a9fbe", "type": "github" }, "original": { @@ -1294,11 +1294,11 @@ "nixpkgs": "nixpkgs_10" }, "locked": { - "lastModified": 1766894905, - "narHash": "sha256-pn8AxxfajqyR/Dmr1wnZYdUXHgM3u6z9x0Z1Ijmz2UQ=", + "lastModified": 1767826491, + "narHash": "sha256-WSBENPotD2MIhZwolL6GC9npqgaS5fkM7j07V2i/Ur8=", "owner": "Mic92", "repo": "sops-nix", - "rev": "61b39c7b657081c2adc91b75dd3ad8a91d6f07a7", + "rev": "ea3adcb6d2a000d9a69d0e23cad1f2cacb3a9fbe", "type": "github" }, "original": { @@ -1326,11 +1326,11 @@ "tinted-zed": "tinted-zed" }, "locked": { - "lastModified": 1766603026, - "narHash": "sha256-J2DDdRqSU4w9NNgkMfmMeaLIof5PXtS9RG7y6ckDvQE=", + "lastModified": 1767886384, + "narHash": "sha256-5/hrrHMZuwwJXqLb86MBElPKS61Efe+hgGkVvpbzJM4=", "owner": "nix-community", "repo": "stylix", - "rev": "551df12ee3ebac52c5712058bd97fd9faa4c3430", + "rev": "a525e4774f2576e0f10b8b183c2dfaf7d165c052", "type": "github" }, "original": { @@ -1517,11 +1517,11 @@ "tinted-schemes": { "flake": false, "locked": { - "lastModified": 1763914658, - "narHash": "sha256-Hju0WtMf3iForxtOwXqGp3Ynipo0EYx1AqMKLPp9BJw=", + "lastModified": 1767710407, + "narHash": "sha256-+W1EB79Jl0/gm4JqmO0Nuc5C7hRdp4vfsV/VdzI+des=", "owner": "tinted-theming", "repo": "schemes", - "rev": "0f6be815d258e435c9b137befe5ef4ff24bea32c", + "rev": "2800e2b8ac90f678d7e4acebe4fa253f602e05b2", "type": "github" }, "original": { @@ -1533,11 +1533,11 @@ "tinted-tmux": { "flake": false, "locked": { - "lastModified": 1764465359, - "narHash": "sha256-lbSVPqLEk2SqMrnpvWuKYGCaAlfWFMA6MVmcOFJjdjE=", + "lastModified": 1767489635, + "narHash": "sha256-e6nnFnWXKBCJjCv4QG4bbcouJ6y3yeT70V9MofL32lU=", "owner": "tinted-theming", "repo": "tinted-tmux", - "rev": "edf89a780e239263cc691a987721f786ddc4f6aa", + "rev": "3c32729ccae99be44fe8a125d20be06f8d7d8184", "type": "github" }, "original": { @@ -1549,11 +1549,11 @@ "tinted-zed": { "flake": false, "locked": { - "lastModified": 1764464512, - "narHash": "sha256-rCD/pAhkMdCx6blsFwxIyvBJbPZZ1oL2sVFrH07lmqg=", + "lastModified": 1767488740, + "narHash": "sha256-wVOj0qyil8m+ouSsVZcNjl5ZR+1GdOOAooAatQXHbuU=", "owner": "tinted-theming", "repo": "base16-zed", - "rev": "907dbba5fb8cf69ebfd90b00813418a412d0a29a", + "rev": "11abb0b282ad3786a2aae088d3a01c60916f2e40", "type": "github" }, "original": { @@ -1570,11 +1570,11 @@ ] }, "locked": { - "lastModified": 1767122417, - "narHash": "sha256-yOt/FTB7oSEKQH9EZMFMeuldK1HGpQs2eAzdS9hNS/o=", + "lastModified": 1767801790, + "narHash": "sha256-QfX6g3Wj2vQe7oBJEbTf0npvC6sJoDbF9hb2+gM5tf8=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "dec15f37015ac2e774c84d0952d57fcdf169b54d", + "rev": "778a1d691f1ef45dd68c661715c5bf8cbf131c80", "type": "github" }, "original": { @@ -1591,11 +1591,11 @@ ] }, "locked": { - "lastModified": 1767381285, - "narHash": "sha256-tdqw7/oQLdJbQqYUA+yGNCHuOjWCTYB1J1U8BAJwHGo=", + "lastModified": 1767763594, + "narHash": "sha256-5ysv8EuVAgDoYmNuXEUNf7vBzdeRaFxeIlIndv5HMvs=", "owner": "0xc000022070", "repo": "zen-browser-flake", - "rev": "92463488fe6ea67b0672345e16c4259e9edcc2ac", + "rev": "8b2302d8c10369c9135552cc892da75cff5ddb03", "type": "github" }, "original": { From ca9dc5c8c15531a0abe117be6fd995d32d1ee080 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Sun, 1 Feb 2026 20:02:08 +0100 Subject: [PATCH 046/108] . --- .sops.yaml | 4 +++- modules/home/application/teamspeak/default.nix | 5 ++++- modules/home/themes/default.nix | 4 ++-- modules/nixos/application/steam/default.nix | 17 +++++++++++------ modules/nixos/desktop/plasma/default.nix | 2 +- packages/studio/default.nix | 9 ++++++--- shells/default/default.nix | 1 + systems/x86_64-linux/manwe/default.nix | 6 ++++-- 8 files changed, 32 insertions(+), 16 deletions(-) diff --git a/.sops.yaml b/.sops.yaml index 9e7956c..1faf874 100644 --- a/.sops.yaml +++ b/.sops.yaml @@ -1,6 +1,7 @@ keys: - &ulmo_1 age19qfpf980tadguqq44zf6xwvjvl428dyrj46ha3n6aeqddwhtnuqqml7etq - &ulmo_2 age1ewes0f5snqx3sh5ul6fa6qtxzhd25829v6mf5rx2wnheat6fefps5rme2x + - &manwe_1 age1jmrmdw4kmjeu9d6z74r2unqt7wpgsx24vqejmdjretsnsn8g4drsl3m98w creation_rules: # All Machine secrets @@ -8,4 +9,5 @@ creation_rules: key_groups: - age: - *ulmo_1 - - *ulmo_2 \ No newline at end of file + - *ulmo_2 + - *manwe_1 diff --git a/modules/home/application/teamspeak/default.nix b/modules/home/application/teamspeak/default.nix index d234e9a..3e5e530 100644 --- a/modules/home/application/teamspeak/default.nix +++ b/modules/home/application/teamspeak/default.nix @@ -10,6 +10,9 @@ in }; config = mkIf cfg.enable { - home.packages = with pkgs; [ teamspeak3 teamspeak6-client ]; + home.packages = with pkgs; [ + # teamspeak3 + teamspeak6-client + ]; }; } diff --git a/modules/home/themes/default.nix b/modules/home/themes/default.nix index 3fb8f15..d338b88 100644 --- a/modules/home/themes/default.nix +++ b/modules/home/themes/default.nix @@ -26,13 +26,13 @@ in { config = mkIf (cfg.enable) { stylix = { - enable = true; + enable = true; base16Scheme = "${pkgs.base16-schemes}/share/themes/${cfg.theme}.yaml"; image = ./${cfg.theme}.jpg; polarity = cfg.polarity; -# targets.qt.platform = mkDefault "kde"; + targets.qt.platform = mkDefault "kde"; targets.zen-browser.profileNames = [ "Chris" ]; fonts = { diff --git a/modules/nixos/application/steam/default.nix b/modules/nixos/application/steam/default.nix index 5b08d31..fc42935 100644 --- a/modules/nixos/application/steam/default.nix +++ b/modules/nixos/application/steam/default.nix @@ -15,15 +15,20 @@ in { }; config = mkIf cfg.enable { + # environment.systemPackages = with pkgs; [ steam ]; + programs = { steam = { enable = true; - package = pkgs.steam.override { - extraEnv = { - DXVK_HUD = "compiler"; - MANGOHUD = true; - }; - }; + remotePlay.openFirewall = true; + dedicatedServer.openFirewall = true; + localNetworkGameTransfers.openFirewall = true; + # package = pkgs.steam.override { + # extraEnv = { + # DXVK_HUD = "compiler"; + # MANGOHUD = true; + # }; + # }; # gamescopeSession = { # enable = true; diff --git a/modules/nixos/desktop/plasma/default.nix b/modules/nixos/desktop/plasma/default.nix index d1e2a28..aa1e497 100644 --- a/modules/nixos/desktop/plasma/default.nix +++ b/modules/nixos/desktop/plasma/default.nix @@ -22,7 +22,7 @@ in konsole kate ghostwriter - oxygen + # oxygen ]; environment.sessionVariables.NIXOS_OZONE_WL = "1"; diff --git a/packages/studio/default.nix b/packages/studio/default.nix index e3061d4..84610a3 100644 --- a/packages/studio/default.nix +++ b/packages/studio/default.nix @@ -11,8 +11,8 @@ in mkWindowsAppNoCC rec { version = "2.25.4"; src = fetchurl { - url = "https://studio.download.bricklink.info/Studio2.0+EarlyAccess/Archive/2.25.4_1/Studio+2.0+EarlyAccess.exe"; - sha256 = "sha256:1gw6pyvfr7zr42g21hqgiwkjs88nvhq2c2v40y21frvwv17hja92"; + url = "https://studio.download.bricklink.info/Studio2.0+EarlyAccess/Archive/2.25.12_1/Studio+2.0+EarlyAccess.exe"; + sha256 = "sha256:1xl3zvzkzr64zphk7rnpfx3whhbaykzw06m3nd5dc12r2p4sdh3v"; }; enableMonoBootPrompt = false; @@ -59,7 +59,10 @@ in mkWindowsAppNoCC rec { wine64 reg add 'HKEY_CURRENT_USER\Software\Wine\X11 Driver' /t REG_SZ /v UseTakeFocus /d N /f ''; - winAppPreRun = ''''; + winAppPreRun = '' + wineserver -W + wine64 reg add 'HKEY_CURRENT_USER\Software\Wine\X11 Driver' /t REG_SZ /v UseTakeFocus /d N /f + ''; winAppRun = '' wine64 "$WINEPREFIX/drive_c/Program Files/Studio 2.0/Studio.exe" "$ARGS" diff --git a/shells/default/default.nix b/shells/default/default.nix index 5bd5b5f..ed12b5c 100644 --- a/shells/default/default.nix +++ b/shells/default/default.nix @@ -17,5 +17,6 @@ mkShell { nixd openssl inputs.clan-core.packages.${stdenv.hostPlatform.system}.clan-cli + nix-output-monitor ]; } diff --git a/systems/x86_64-linux/manwe/default.nix b/systems/x86_64-linux/manwe/default.nix index a2c478f..179e410 100644 --- a/systems/x86_64-linux/manwe/default.nix +++ b/systems/x86_64-linux/manwe/default.nix @@ -1,4 +1,4 @@ -{...}: { +{ pkgs, ...}: { imports = [ ./disks.nix ./hardware.nix @@ -8,6 +8,8 @@ services.logrotate.checkConfig = false; + environment.systemPackages = with pkgs; [ beyond-all-reason ]; + sneeuwvlok = { hardware.has = { gpu.amd = true; @@ -20,7 +22,7 @@ animated = true; }; - desktop.use = "cosmic"; + desktop.use = "plasma"; application = { steam.enable = true; From 96f2eee2b6accc44b487fa0475d7cde539ee4874 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Sun, 1 Feb 2026 20:03:20 +0100 Subject: [PATCH 047/108] chore: update dependencies --- flake.lock | 347 +++++++++++++++++++++-------------------------------- 1 file changed, 140 insertions(+), 207 deletions(-) diff --git a/flake.lock b/flake.lock index 573cd43..581ab34 100644 --- a/flake.lock +++ b/flake.lock @@ -75,7 +75,6 @@ "flake-parts": "flake-parts", "nix-darwin": "nix-darwin", "nix-select": "nix-select", - "nixos-facter-modules": "nixos-facter-modules", "nixpkgs": [ "nixpkgs" ], @@ -84,11 +83,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1767876003, - "narHash": "sha256-ZXotZl4Ipylax0BLUlUHtpw+Lm2qHVTmbX8LuZI8He4=", - "rev": "9ac4c3ceafdc9f50eab90f70fecc182e38169622", + "lastModified": 1769958310, + "narHash": "sha256-JonUM+qE5RmN0ND8bpbXRV7gKG93t/++m9YDmaNfm8s=", + "rev": "b66861cef5c0c8457a1041c941e308f245e6f587", "type": "tarball", - "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/9ac4c3ceafdc9f50eab90f70fecc182e38169622.tar.gz" + "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/b66861cef5c0c8457a1041c941e308f245e6f587.tar.gz" }, "original": { "type": "tarball", @@ -126,11 +125,11 @@ ] }, "locked": { - "lastModified": 1767582502, - "narHash": "sha256-WVcYGWcAlWzVt38OaTC5i5Q3QkIKJKZsJ7LcQZVVxeE=", - "rev": "31f2e3ecf207fd2760e3cebf7c2cf3cb7170ea3d", + "lastModified": 1769701076, + "narHash": "sha256-ZquoXeXZ8fwMQ54UVgcGRKjzdK0deRHzm0a2jVbw4uw=", + "rev": "21655e76e84749d5ce3c9b3aaf9d86ba4016ba08", "type": "tarball", - "url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/31f2e3ecf207fd2760e3cebf7c2cf3cb7170ea3d.tar.gz" + "url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/21655e76e84749d5ce3c9b3aaf9d86ba4016ba08.tar.gz" }, "original": { "type": "tarball", @@ -145,11 +144,11 @@ ] }, "locked": { - "lastModified": 1766150702, - "narHash": "sha256-P0kM+5o+DKnB6raXgFEk3azw8Wqg5FL6wyl9jD+G5a4=", + "lastModified": 1769524058, + "narHash": "sha256-zygdD6X1PcVNR2PsyK4ptzrVEiAdbMqLos7utrMDEWE=", "owner": "nix-community", "repo": "disko", - "rev": "916506443ecd0d0b4a0f4cf9d40a3c22ce39b378", + "rev": "71a3fc97d80881e91710fe721f1158d3b96ae14d", "type": "github" }, "original": { @@ -161,14 +160,15 @@ "erosanix": { "inputs": { "flake-compat": "flake-compat", - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs", + "tiny-audio-player": "tiny-audio-player" }, "locked": { - "lastModified": 1767731360, - "narHash": "sha256-L8PmjsBDqWt9gBCp+0wSW6WXA9iVylceltk98HFRP4Y=", + "lastModified": 1769964530, + "narHash": "sha256-U0kMoegznbJpp1dOdTP0fle17m+1zh0buEfNwGkBWac=", "owner": "emmanuelrosa", "repo": "erosanix", - "rev": "4a43987a3b8efa816534d82c696ad48f1152f457", + "rev": "ebc2a55cfc7ec12c387e6711c2372c80c1d9d9d0", "type": "github" }, "original": { @@ -185,11 +185,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1767854969, - "narHash": "sha256-kJQ920Ymo4rrgFlSdNw/ZYpdd1uWL5Ik1VKpZaKsQ0c=", + "lastModified": 1769966877, + "narHash": "sha256-saM3CldynDtMFHRc25UdQ7EQtP5o+oSUgsHTMvIzzXw=", "owner": "nix-community", "repo": "fenix", - "rev": "1ad578f9643053f87843215b1786fd8e66ddb384", + "rev": "8a42e00e442d416e6c838fc6b40240da65aacbcd", "type": "github" }, "original": { @@ -205,11 +205,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1767810592, - "narHash": "sha256-bBuww/tHGv4N5tXFbOkWen4KKF9TfdR4xnXimTP+WVI=", + "lastModified": 1769944836, + "narHash": "sha256-c8I7SjcU0Qn1exWexyxikRjJPk+8bcoz/YTa9kpQA3g=", "owner": "nix-community", "repo": "flake-firefox-nightly", - "rev": "1a411ec44c27e123f7136027276bb56e1b76861b", + "rev": "c4c973dda2b99b25c1420d948a566b4043e20f16", "type": "github" }, "original": { @@ -321,11 +321,11 @@ ] }, "locked": { - "lastModified": 1767609335, - "narHash": "sha256-feveD98mQpptwrAEggBQKJTYbvwwglSbOv53uCfH9PY=", + "lastModified": 1768135262, + "narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "250481aafeb741edfe23d29195671c19b36b6dca", + "rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac", "type": "github" }, "original": { @@ -363,11 +363,11 @@ ] }, "locked": { - "lastModified": 1760948891, - "narHash": "sha256-TmWcdiUUaWk8J4lpjzu4gCGxWY6/Ok7mOK4fIFfBuU4=", + "lastModified": 1768135262, + "narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "864599284fc7c0ba6357ed89ed5e2cd5040f0c04", + "rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac", "type": "github" }, "original": { @@ -438,7 +438,7 @@ }, "flake-utils-plus": { "inputs": { - "flake-utils": "flake-utils_4" + "flake-utils": "flake-utils_2" }, "locked": { "lastModified": 1715533576, @@ -457,43 +457,7 @@ }, "flake-utils_2": { "inputs": { - "systems": "systems_3" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flake-utils_3": { - "inputs": { - "systems": "systems_4" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flake-utils_4": { - "inputs": { - "systems": "systems_6" + "systems": "systems_5" }, "locked": { "lastModified": 1694529238, @@ -583,18 +547,16 @@ }, "himmelblau": { "inputs": { - "flake-utils": "flake-utils_2", "nixpkgs": [ "nixpkgs" - ], - "rust-overlay": "rust-overlay" + ] }, "locked": { - "lastModified": 1767649346, - "narHash": "sha256-hzu/Z8yhR3sRU4RFGwIrVkX+ovxrWtT7nMWLOvUwHEc=", + "lastModified": 1769812538, + "narHash": "sha256-JeLwoP/oTzAyHrWvqMyfgUgnwqpmTZBNdRHNE7IhesA=", "owner": "himmelblau-idm", "repo": "himmelblau", - "rev": "df0bee9fdd170a92ff99b9526510f030d29b6d4a", + "rev": "1ba4e5d20b1fbe51e26c0959054e0ab10d909e8d", "type": "github" }, "original": { @@ -610,11 +572,11 @@ ] }, "locked": { - "lastModified": 1767878997, - "narHash": "sha256-pOWcb+hKeRM40eo6+aqTVcGPS1pCPoMOJHvP3So7VBM=", + "lastModified": 1769952507, + "narHash": "sha256-eNTfxT3v8b7s1dqswgposi5Y1CUMoOUhQKiy29QY25U=", "owner": "nix-community", "repo": "home-manager", - "rev": "d1da1de5c27bcedfbf8d8f260b63b51ee5442550", + "rev": "b59376563943ce163b2553aeb63d0c170967d74e", "type": "github" }, "original": { @@ -631,11 +593,11 @@ ] }, "locked": { - "lastModified": 1767104570, - "narHash": "sha256-GKgwu5//R+cLdKysZjGqvUEEOGXXLdt93sNXeb2M/Lk=", + "lastModified": 1769872935, + "narHash": "sha256-07HMIGQ/WJeAQJooA7Kkg1SDKxhAiV6eodvOwTX6WKI=", "owner": "nix-community", "repo": "home-manager", - "rev": "e4e78a2cbeaddd07ab7238971b16468cc1d14daf", + "rev": "f4ad5068ee8e89e4a7c2e963e10dd35cd77b37b7", "type": "github" }, "original": { @@ -652,11 +614,11 @@ ] }, "locked": { - "lastModified": 1767777502, - "narHash": "sha256-jXb2kBU6lO6Q6S9zoR/bhVLMjg2hM9EW8gWIwsmkj64=", + "lastModified": 1769857393, + "narHash": "sha256-3sgdsShDEyA/Jd+VKS8cI2GYHfkS482zH80QcXBF77E=", "owner": "Jovian-Experiments", "repo": "Jovian-NixOS", - "rev": "a81fad3f4a70fdaa779e74b7da2063fa2e358028", + "rev": "91d6a007c918d3e862ec2418babfe271a4f7bfaa", "type": "github" }, "original": { @@ -671,11 +633,11 @@ "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1767530944, - "narHash": "sha256-DPczJq4hmhqG9i7wo+zARji+hUj1iKR3Qnz8BTuFgHU=", + "lastModified": 1769345475, + "narHash": "sha256-PDXFBwnWMQHRMcg1yxg2+u0YrgnmjvhMJJj27RbI7rc=", "owner": "nix-community", "repo": "lib-aggregate", - "rev": "f32c12294f38b793121079bcf98fcb30d5440edc", + "rev": "d708384d89236a8f161173172e780415cffeec2f", "type": "github" }, "original": { @@ -705,11 +667,11 @@ }, "mnw": { "locked": { - "lastModified": 1758834834, - "narHash": "sha256-Y7IvY4F8vajZyp3WGf+KaiIVwondEkMFkt92Cr9NZmg=", + "lastModified": 1768701608, + "narHash": "sha256-kSvWF3Xt2HW9hmV5V7i8PqeWJIBUKmuKoHhOgj3Znzs=", "owner": "Gerg-L", "repo": "mnw", - "rev": "cfbc7d1cc832e318d0863a5fc91d940a96034001", + "rev": "20d63a8a1ae400557c770052a46a9840e768926b", "type": "github" }, "original": { @@ -745,15 +707,16 @@ ] }, "locked": { - "lastModified": 1765720983, - "narHash": "sha256-tWtukpABmux6EC/FuCJEgA1kmRjcRPtED44N+GGPq+4=", + "lastModified": 1768214250, + "narHash": "sha256-hnBZDQWUxJV3KbtvyGW5BKLO/fAwydrxm5WHCWMQTbw=", "owner": "feel-co", "repo": "ndg", - "rev": "f399ace8bb8e1f705dd8942b24d207aa4d75c936", + "rev": "a6bd3c1ce2668d096e4fdaaa03ad7f03ba1fbca8", "type": "github" }, "original": { "owner": "feel-co", + "ref": "refs/tags/v2.6.0", "repo": "ndg", "type": "github" } @@ -766,11 +729,11 @@ ] }, "locked": { - "lastModified": 1767718503, - "narHash": "sha256-V+VkFs0aSG0ca8p/N3gib7FAf4cq9jyr5Gm+ZBrHQpo=", + "lastModified": 1768764703, + "narHash": "sha256-5ulSDyOG1U+1sJhkJHYsUOWEsmtLl97O0NTVMvgIVyc=", "owner": "nix-darwin", "repo": "nix-darwin", - "rev": "9f48ffaca1f44b3e590976b4da8666a9e86e6eb1", + "rev": "0fc4e7ac670a0ed874abacf73c4b072a6a58064b", "type": "github" }, "original": { @@ -804,15 +767,15 @@ "nix-minecraft": { "inputs": { "flake-compat": "flake-compat_3", - "flake-utils": "flake-utils_3", - "nixpkgs": "nixpkgs_7" + "nixpkgs": "nixpkgs_7", + "systems": "systems_3" }, "locked": { - "lastModified": 1767838769, - "narHash": "sha256-KCLU6SUU80tEBKIVZsBrSjRYX6kn1eVIYI3fEEqOp24=", + "lastModified": 1769936216, + "narHash": "sha256-ezgoxbHXGQMufga7aaZNhYZYiXQhaGV4jLAZy0fBhzA=", "owner": "Infinidoge", "repo": "nix-minecraft", - "rev": "4da21f019f6443f513f16af7f220ba4db1cdfc04", + "rev": "ff6604fa8d25c1e1c135a261ddd6b3dcc4443129", "type": "github" }, "original": { @@ -864,21 +827,6 @@ "type": "github" } }, - "nixos-facter-modules": { - "locked": { - "lastModified": 1766558141, - "narHash": "sha256-Ud9v49ZPsoDBFuyJSQ2Mpw1ZgAH/aMwUwwzrVoetNus=", - "owner": "nix-community", - "repo": "nixos-facter-modules", - "rev": "e796d536e3d83de74267069e179dc620a608ed7d", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "nixos-facter-modules", - "type": "github" - } - }, "nixos-generators": { "inputs": { "nixlib": "nixlib", @@ -887,11 +835,11 @@ ] }, "locked": { - "lastModified": 1764234087, - "narHash": "sha256-NHF7QWa0ZPT8hsJrvijREW3+nifmF2rTXgS2v0tpcEA=", + "lastModified": 1769813415, + "narHash": "sha256-nnVmNNKBi1YiBNPhKclNYDORoHkuKipoz7EtVnXO50A=", "owner": "nix-community", "repo": "nixos-generators", - "rev": "032a1878682fafe829edfcf5fdfad635a2efe748", + "rev": "8946737ff703382fda7623b9fab071d037e897d5", "type": "github" }, "original": { @@ -908,11 +856,11 @@ ] }, "locked": { - "lastModified": 1765841014, - "narHash": "sha256-55V0AJ36V5Egh4kMhWtDh117eE3GOjwq5LhwxDn9eHg=", + "lastModified": 1769217863, + "narHash": "sha256-RY9kJDXD6+2Td/59LkZ0PFSereCXHdBX9wIkbYjRKCY=", "owner": "nix-community", "repo": "nixos-wsl", - "rev": "be4af8042e7a61fa12fda58fe9a3b3babdefe17b", + "rev": "38a5250e57f583662eac3b944830e4b9e169e965", "type": "github" }, "original": { @@ -923,11 +871,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1767276799, - "narHash": "sha256-FW1bq43LRGMwA+SNoe64fMsM19/5QS+8rwfECmiukxk=", + "lastModified": 1769914019, + "narHash": "sha256-w3TySosUsTuVdWAoHEVxvPIX42lCv/98Rmt5LRu3Bw8=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "af559d367a9ff45e6e0f8c5b214d12dfa6ac4155", + "rev": "17317ace7fb805f192bcf5595e41d18b09f9b497", "type": "github" }, "original": { @@ -939,11 +887,11 @@ }, "nixpkgs-lib": { "locked": { - "lastModified": 1767489562, - "narHash": "sha256-vyCgu90Lv0poRd3Ne2rQDUj0Jfj+Oilgtj3M0CoL2SM=", + "lastModified": 1769304017, + "narHash": "sha256-TE1EHvIAz81IGUKTmKQehbc9hjuxF7pe/QWdQuy/Ijc=", "owner": "nix-community", "repo": "nixpkgs.lib", - "rev": "c0acf4617d3542ad4c6be461c1a9b9059c937060", + "rev": "d4c8053ce1d9ba28bfb69a9f9f23ac24d313d4e8", "type": "github" }, "original": { @@ -954,11 +902,11 @@ }, "nixpkgs_10": { "locked": { - "lastModified": 1767364772, - "narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=", + "lastModified": 1769740369, + "narHash": "sha256-xKPyJoMoXfXpDM5DFDZDsi9PHArf2k5BJjvReYXoFpM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "16c7794d0a28b5a37904d55bcca36003b9109aaa", + "rev": "6308c3b21396534d8aaeac46179c14c439a89b8a", "type": "github" }, "original": { @@ -986,11 +934,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1767767207, - "narHash": "sha256-Mj3d3PfwltLmukFal5i3fFt27L6NiKXdBezC1EBuZs4=", + "lastModified": 1769900851, + "narHash": "sha256-RgCgXS3WiG9c/1wxFM6OXmmv39dSaLLON9VeAbTTAIM=", "owner": "nixos", "repo": "nixpkgs", - "rev": "5912c1772a44e31bf1c63c0390b90501e5026886", + "rev": "30a3e96da641620c63f2e1f345ea434ac78f5de1", "type": "github" }, "original": { @@ -1033,11 +981,11 @@ }, "nixpkgs_5": { "locked": { - "lastModified": 1767887613, - "narHash": "sha256-QlxRV82Ad8BlMBBvwMPh8JcnAsJnZmx8FlJSOPAl2KE=", + "lastModified": 1769972154, + "narHash": "sha256-PphDbFMES0NPP/3JCNkfNdagrDepG9rbLvJSNG0LhuI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "417662c695a7f10ada76b30f866ac65f92d61883", + "rev": "8e4f0a16f0236fe4b52dde572fb53464bd5c7440", "type": "github" }, "original": { @@ -1065,11 +1013,11 @@ }, "nixpkgs_7": { "locked": { - "lastModified": 1748929857, - "narHash": "sha256-lcZQ8RhsmhsK8u7LIFsJhsLh/pzR9yZ8yqpTzyGdj+Q=", + "lastModified": 1769461804, + "narHash": "sha256-msG8SU5WsBUfVVa/9RPLaymvi5bI8edTavbIq3vRlhI=", "owner": "nixos", "repo": "nixpkgs", - "rev": "c2a03962b8e24e669fb37b7df10e7c79531ff1a4", + "rev": "bfc1b8a4574108ceef22f02bafcf6611380c100d", "type": "github" }, "original": { @@ -1081,11 +1029,11 @@ }, "nixpkgs_8": { "locked": { - "lastModified": 1767767207, - "narHash": "sha256-Mj3d3PfwltLmukFal5i3fFt27L6NiKXdBezC1EBuZs4=", + "lastModified": 1769789167, + "narHash": "sha256-kKB3bqYJU5nzYeIROI82Ef9VtTbu4uA3YydSk/Bioa8=", "owner": "nixos", "repo": "nixpkgs", - "rev": "5912c1772a44e31bf1c63c0390b90501e5026886", + "rev": "62c8382960464ceb98ea593cb8321a2cf8f9e3e5", "type": "github" }, "original": { @@ -1097,16 +1045,16 @@ }, "nixpkgs_9": { "locked": { - "lastModified": 1767364772, - "narHash": "sha256-fFUnEYMla8b7UKjijLnMe+oVFOz6HjijGGNS1l7dYaQ=", + "lastModified": 1769461804, + "narHash": "sha256-msG8SU5WsBUfVVa/9RPLaymvi5bI8edTavbIq3vRlhI=", "owner": "nixos", "repo": "nixpkgs", - "rev": "16c7794d0a28b5a37904d55bcca36003b9109aaa", + "rev": "bfc1b8a4574108ceef22f02bafcf6611380c100d", "type": "github" }, "original": { "owner": "nixos", - "ref": "nixpkgs-unstable", + "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } @@ -1143,14 +1091,14 @@ "mnw": "mnw", "ndg": "ndg", "nixpkgs": "nixpkgs_9", - "systems": "systems_5" + "systems": "systems_4" }, "locked": { - "lastModified": 1767847386, - "narHash": "sha256-S8lf6YtZpJQaq38GCuao+h7LnNYFVvTpI70lNevk5xM=", + "lastModified": 1769915470, + "narHash": "sha256-rB851Mo2DKNfaUwfzCr9Uiya/LjRy0t7U4v1j5nDWuY=", "owner": "notashelf", "repo": "nvf", - "rev": "317877430a36e2e449405aaea30788119791dedc", + "rev": "8aae70026da3769bf16530aa513ffedeb95dd84c", "type": "github" }, "original": { @@ -1169,11 +1117,11 @@ ] }, "locked": { - "lastModified": 1767662275, - "narHash": "sha256-d5Q1GmQ+sW1Bt8cgDE0vOihzLaswsm8cSdg8124EqXE=", + "lastModified": 1769956244, + "narHash": "sha256-12RCFLyAedyMOdenUi7cN3ioJPEGjA/ZG1BLjugfUVs=", "owner": "nix-community", "repo": "plasma-manager", - "rev": "51816be33a1ff0d4b22427de83222d5bfa96d30e", + "rev": "fe54ea85c6e4413fba03b84d50f2b431d2f7c831", "type": "github" }, "original": { @@ -1211,11 +1159,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1767830331, - "narHash": "sha256-9XF16XbXvXK3UOLFZpKnFOvvQrxD8J4Lbn+brzL1rG0=", + "lastModified": 1769857242, + "narHash": "sha256-3eKpRRzKz0KzY7CJzRXFm4POwEqbuTohyQ2ajI/zKvg=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "54f5eea26321c33a4a4acc826b9a393cb874f85d", + "rev": "17304e9c7e11d26139672d3d77aa498b1cae0d69", "type": "github" }, "original": { @@ -1225,27 +1173,6 @@ "type": "github" } }, - "rust-overlay": { - "inputs": { - "nixpkgs": [ - "himmelblau", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1759977258, - "narHash": "sha256-hOxEFSEBoqDmJb7BGX1CzT1gvUPK6r+Qs+n3IxBgfTs=", - "owner": "oxalica", - "repo": "rust-overlay", - "rev": "1d0c6173f57d07db7957b50e799240d4f2d7520f", - "type": "github" - }, - "original": { - "owner": "oxalica", - "repo": "rust-overlay", - "type": "github" - } - }, "snowfall-lib": { "inputs": { "flake-compat": "flake-compat_5", @@ -1276,11 +1203,11 @@ ] }, "locked": { - "lastModified": 1767826491, - "narHash": "sha256-WSBENPotD2MIhZwolL6GC9npqgaS5fkM7j07V2i/Ur8=", + "lastModified": 1769921679, + "narHash": "sha256-twBMKGQvaztZQxFxbZnkg7y/50BW9yjtCBWwdjtOZew=", "owner": "Mic92", "repo": "sops-nix", - "rev": "ea3adcb6d2a000d9a69d0e23cad1f2cacb3a9fbe", + "rev": "1e89149dcfc229e7e2ae24a8030f124a31e4f24f", "type": "github" }, "original": { @@ -1294,11 +1221,11 @@ "nixpkgs": "nixpkgs_10" }, "locked": { - "lastModified": 1767826491, - "narHash": "sha256-WSBENPotD2MIhZwolL6GC9npqgaS5fkM7j07V2i/Ur8=", + "lastModified": 1769921679, + "narHash": "sha256-twBMKGQvaztZQxFxbZnkg7y/50BW9yjtCBWwdjtOZew=", "owner": "Mic92", "repo": "sops-nix", - "rev": "ea3adcb6d2a000d9a69d0e23cad1f2cacb3a9fbe", + "rev": "1e89149dcfc229e7e2ae24a8030f124a31e4f24f", "type": "github" }, "original": { @@ -1318,7 +1245,7 @@ "gnome-shell": "gnome-shell", "nixpkgs": "nixpkgs_11", "nur": "nur", - "systems": "systems_7", + "systems": "systems_6", "tinted-foot": "tinted-foot", "tinted-kitty": "tinted-kitty", "tinted-schemes": "tinted-schemes", @@ -1326,11 +1253,11 @@ "tinted-zed": "tinted-zed" }, "locked": { - "lastModified": 1767886384, - "narHash": "sha256-5/hrrHMZuwwJXqLb86MBElPKS61Efe+hgGkVvpbzJM4=", + "lastModified": 1769888473, + "narHash": "sha256-4KWbaJwaYnZ60bFyTudZYAKskjr7Sa17R3/yh+oXS7w=", "owner": "nix-community", "repo": "stylix", - "rev": "a525e4774f2576e0f10b8b183c2dfaf7d165c052", + "rev": "ae5c0239ae4f82a8c7e33ad8a456535d5a9ba813", "type": "github" }, "original": { @@ -1444,28 +1371,13 @@ "type": "github" } }, - "systems_8": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, "terranix": { "inputs": { "flake-parts": "flake-parts_5", "nixpkgs": [ "nixpkgs" ], - "systems": "systems_8" + "systems": "systems_7" }, "locked": { "lastModified": 1762472226, @@ -1562,6 +1474,27 @@ "type": "github" } }, + "tiny-audio-player": { + "inputs": { + "nixpkgs": [ + "erosanix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1769797635, + "narHash": "sha256-ENwjbVYOks9bkBAIpfwQg5uCHrU8iG7zq/5toKmdoZk=", + "owner": "emmanuelrosa", + "repo": "tiny_audio_player", + "rev": "350c1697d0a0181d55bda191b10a75cfcb8c9c50", + "type": "github" + }, + "original": { + "owner": "emmanuelrosa", + "repo": "tiny_audio_player", + "type": "github" + } + }, "treefmt-nix": { "inputs": { "nixpkgs": [ @@ -1570,11 +1503,11 @@ ] }, "locked": { - "lastModified": 1767801790, - "narHash": "sha256-QfX6g3Wj2vQe7oBJEbTf0npvC6sJoDbF9hb2+gM5tf8=", + "lastModified": 1769691507, + "narHash": "sha256-8aAYwyVzSSwIhP2glDhw/G0i5+wOrren3v6WmxkVonM=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "778a1d691f1ef45dd68c661715c5bf8cbf131c80", + "rev": "28b19c5844cc6e2257801d43f2772a4b4c050a1b", "type": "github" }, "original": { @@ -1591,11 +1524,11 @@ ] }, "locked": { - "lastModified": 1767763594, - "narHash": "sha256-5ysv8EuVAgDoYmNuXEUNf7vBzdeRaFxeIlIndv5HMvs=", + "lastModified": 1769922110, + "narHash": "sha256-/0Cl75Yy4mQOWNfr2ZR5aYZlFc2geH7NUkwiwiKUNhg=", "owner": "0xc000022070", "repo": "zen-browser-flake", - "rev": "8b2302d8c10369c9135552cc892da75cff5ddb03", + "rev": "dc3cb779f0fae72b3ebffd60a2272095f8848eda", "type": "github" }, "original": { From 4b248c2322e872e7eebe8ec547e6c5c9514cd135 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Tue, 3 Feb 2026 14:19:15 +0100 Subject: [PATCH 048/108] feat(forgejo): enable lfs --- modules/nixos/services/development/forgejo/default.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/nixos/services/development/forgejo/default.nix b/modules/nixos/services/development/forgejo/default.nix index 114726e..dfae9f0 100644 --- a/modules/nixos/services/development/forgejo/default.nix +++ b/modules/nixos/services/development/forgejo/default.nix @@ -35,6 +35,7 @@ in { services = { forgejo = { enable = true; + lfs.enable = true; useWizard = false; database.type = "postgres"; From 5898443d46f2f71c99cbc00d4af3dd2d52d73ec7 Mon Sep 17 00:00:00 2001 From: chris Date: Mon, 9 Feb 2026 14:09:53 +0000 Subject: [PATCH 049/108] chore: update dependencies --- flake.lock | 166 ++++++++++++++++++++++++++--------------------------- 1 file changed, 83 insertions(+), 83 deletions(-) diff --git a/flake.lock b/flake.lock index 581ab34..ae2abb3 100644 --- a/flake.lock +++ b/flake.lock @@ -83,11 +83,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1769958310, - "narHash": "sha256-JonUM+qE5RmN0ND8bpbXRV7gKG93t/++m9YDmaNfm8s=", - "rev": "b66861cef5c0c8457a1041c941e308f245e6f587", + "lastModified": 1770634898, + "narHash": "sha256-v05MDDR9Sv8adHsMTNoCHOy4DH5nqOtvaGMeKk4sC4s=", + "rev": "5edc04cc6a1183ad85322deed75a9d4824f7e9f7", "type": "tarball", - "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/b66861cef5c0c8457a1041c941e308f245e6f587.tar.gz" + "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/5edc04cc6a1183ad85322deed75a9d4824f7e9f7.tar.gz" }, "original": { "type": "tarball", @@ -125,11 +125,11 @@ ] }, "locked": { - "lastModified": 1769701076, - "narHash": "sha256-ZquoXeXZ8fwMQ54UVgcGRKjzdK0deRHzm0a2jVbw4uw=", - "rev": "21655e76e84749d5ce3c9b3aaf9d86ba4016ba08", + "lastModified": 1770409579, + "narHash": "sha256-reWzIb3dxJnLcwBEuT6khzEDvCiBCVTiqBR9C4vH/jg=", + "rev": "5065ddc67a7009fb81a29f43aa056b2a4552ed96", "type": "tarball", - "url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/21655e76e84749d5ce3c9b3aaf9d86ba4016ba08.tar.gz" + "url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/5065ddc67a7009fb81a29f43aa056b2a4552ed96.tar.gz" }, "original": { "type": "tarball", @@ -164,11 +164,11 @@ "tiny-audio-player": "tiny-audio-player" }, "locked": { - "lastModified": 1769964530, - "narHash": "sha256-U0kMoegznbJpp1dOdTP0fle17m+1zh0buEfNwGkBWac=", + "lastModified": 1770584961, + "narHash": "sha256-5/ZAb9j1ih+14Ma34iNOgotA3BjQpayqg1O9+e2d7jU=", "owner": "emmanuelrosa", "repo": "erosanix", - "rev": "ebc2a55cfc7ec12c387e6711c2372c80c1d9d9d0", + "rev": "394debf46a32b883dc572472cbda18482eb2de92", "type": "github" }, "original": { @@ -185,11 +185,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1769966877, - "narHash": "sha256-saM3CldynDtMFHRc25UdQ7EQtP5o+oSUgsHTMvIzzXw=", + "lastModified": 1770621632, + "narHash": "sha256-pp7visGpp5SYL1O/eF1ZyiSqk4AJ5xkEJXw7pw0f4EI=", "owner": "nix-community", "repo": "fenix", - "rev": "8a42e00e442d416e6c838fc6b40240da65aacbcd", + "rev": "de681afb16166786926b05a0b528545ad511507a", "type": "github" }, "original": { @@ -205,11 +205,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1769944836, - "narHash": "sha256-c8I7SjcU0Qn1exWexyxikRjJPk+8bcoz/YTa9kpQA3g=", + "lastModified": 1770642320, + "narHash": "sha256-CgL4Y8mdt7ty4uxp4NfUXKhrSar6TMUtCgmh0M16JGo=", "owner": "nix-community", "repo": "flake-firefox-nightly", - "rev": "c4c973dda2b99b25c1420d948a566b4043e20f16", + "rev": "aef5ff9c6122e50fcef4e06d73435cb6cbfbc888", "type": "github" }, "original": { @@ -321,11 +321,11 @@ ] }, "locked": { - "lastModified": 1768135262, - "narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=", + "lastModified": 1769996383, + "narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac", + "rev": "57928607ea566b5db3ad13af0e57e921e6b12381", "type": "github" }, "original": { @@ -552,11 +552,11 @@ ] }, "locked": { - "lastModified": 1769812538, - "narHash": "sha256-JeLwoP/oTzAyHrWvqMyfgUgnwqpmTZBNdRHNE7IhesA=", + "lastModified": 1770420941, + "narHash": "sha256-aWgduwwaVAgdbGInybYpD7zWY1WXs1ZM7vQkkpfWKyk=", "owner": "himmelblau-idm", "repo": "himmelblau", - "rev": "1ba4e5d20b1fbe51e26c0959054e0ab10d909e8d", + "rev": "a6a15bc28852010c2aaa643991123d0e4ab06692", "type": "github" }, "original": { @@ -572,11 +572,11 @@ ] }, "locked": { - "lastModified": 1769952507, - "narHash": "sha256-eNTfxT3v8b7s1dqswgposi5Y1CUMoOUhQKiy29QY25U=", + "lastModified": 1770642890, + "narHash": "sha256-XWWHZEy5ZYMOx5hVuz+oeKtKDfv7syl7dwKCBx0LqzA=", "owner": "nix-community", "repo": "home-manager", - "rev": "b59376563943ce163b2553aeb63d0c170967d74e", + "rev": "13a1beb7c9962e0d2ba35a4d5c87546509b89b7d", "type": "github" }, "original": { @@ -614,11 +614,11 @@ ] }, "locked": { - "lastModified": 1769857393, - "narHash": "sha256-3sgdsShDEyA/Jd+VKS8cI2GYHfkS482zH80QcXBF77E=", + "lastModified": 1770620462, + "narHash": "sha256-6oT0qd5nRpn+smwnUWgiYgN8+PCyNxjRCiaWkqlijAc=", "owner": "Jovian-Experiments", "repo": "Jovian-NixOS", - "rev": "91d6a007c918d3e862ec2418babfe271a4f7bfaa", + "rev": "d58e0f3603bca6caf57aff6bf3c7705004e46f93", "type": "github" }, "original": { @@ -633,11 +633,11 @@ "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1769345475, - "narHash": "sha256-PDXFBwnWMQHRMcg1yxg2+u0YrgnmjvhMJJj27RbI7rc=", + "lastModified": 1770555544, + "narHash": "sha256-ebtYu7XDrNMKgQ1ZStwHbD53uofYKVZudhuvMCXR3NA=", "owner": "nix-community", "repo": "lib-aggregate", - "rev": "d708384d89236a8f161173172e780415cffeec2f", + "rev": "724c7b7f76794102b60482233a0e226056ca5b0c", "type": "github" }, "original": { @@ -667,11 +667,11 @@ }, "mnw": { "locked": { - "lastModified": 1768701608, - "narHash": "sha256-kSvWF3Xt2HW9hmV5V7i8PqeWJIBUKmuKoHhOgj3Znzs=", + "lastModified": 1769981889, + "narHash": "sha256-ndI7AxL/6auelkLHngdUGVImBiHkG8w2N2fOTKZKn4k=", "owner": "Gerg-L", "repo": "mnw", - "rev": "20d63a8a1ae400557c770052a46a9840e768926b", + "rev": "332fed8f43b77149c582f1782683d6aeee1f07cf", "type": "github" }, "original": { @@ -729,11 +729,11 @@ ] }, "locked": { - "lastModified": 1768764703, - "narHash": "sha256-5ulSDyOG1U+1sJhkJHYsUOWEsmtLl97O0NTVMvgIVyc=", + "lastModified": 1770184146, + "narHash": "sha256-DsqnN6LvXmohTRaal7tVZO/AKBuZ02kPBiZKSU4qa/k=", "owner": "nix-darwin", "repo": "nix-darwin", - "rev": "0fc4e7ac670a0ed874abacf73c4b072a6a58064b", + "rev": "0d7874ef7e3ba02d58bebb871e6e29da36fa1b37", "type": "github" }, "original": { @@ -771,11 +771,11 @@ "systems": "systems_3" }, "locked": { - "lastModified": 1769936216, - "narHash": "sha256-ezgoxbHXGQMufga7aaZNhYZYiXQhaGV4jLAZy0fBhzA=", + "lastModified": 1770520993, + "narHash": "sha256-ks1ZFBYlBmQ4CAM4WSmCFUtkUJzbmJ0VJH/JkKVMPqY=", "owner": "Infinidoge", "repo": "nix-minecraft", - "rev": "ff6604fa8d25c1e1c135a261ddd6b3dcc4443129", + "rev": "b32f4325880b4fac47b8736161a8f032dd248b70", "type": "github" }, "original": { @@ -856,11 +856,11 @@ ] }, "locked": { - "lastModified": 1769217863, - "narHash": "sha256-RY9kJDXD6+2Td/59LkZ0PFSereCXHdBX9wIkbYjRKCY=", + "lastModified": 1770645108, + "narHash": "sha256-j19Q1HZNfMxoG1WOGFUF1HPZ/wHkVlLDqjvNhrq5frA=", "owner": "nix-community", "repo": "nixos-wsl", - "rev": "38a5250e57f583662eac3b944830e4b9e169e965", + "rev": "7cfb408f6a5cd243a727aa3397f4c04f5bfccf28", "type": "github" }, "original": { @@ -887,11 +887,11 @@ }, "nixpkgs-lib": { "locked": { - "lastModified": 1769304017, - "narHash": "sha256-TE1EHvIAz81IGUKTmKQehbc9hjuxF7pe/QWdQuy/Ijc=", + "lastModified": 1770515899, + "narHash": "sha256-hbmM5OSFCXIyoYvmZyQL9mjQ2mh/L1+2/4gf/BpXWNE=", "owner": "nix-community", "repo": "nixpkgs.lib", - "rev": "d4c8053ce1d9ba28bfb69a9f9f23ac24d313d4e8", + "rev": "fd928847a8e03461e4b37699e6218539b610217d", "type": "github" }, "original": { @@ -902,11 +902,11 @@ }, "nixpkgs_10": { "locked": { - "lastModified": 1769740369, - "narHash": "sha256-xKPyJoMoXfXpDM5DFDZDsi9PHArf2k5BJjvReYXoFpM=", + "lastModified": 1770380644, + "narHash": "sha256-P7dWMHRUWG5m4G+06jDyThXO7kwSk46C1kgjEWcybkE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "6308c3b21396534d8aaeac46179c14c439a89b8a", + "rev": "ae67888ff7ef9dff69b3cf0cc0fbfbcd3a722abe", "type": "github" }, "original": { @@ -934,11 +934,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1769900851, - "narHash": "sha256-RgCgXS3WiG9c/1wxFM6OXmmv39dSaLLON9VeAbTTAIM=", + "lastModified": 1770625969, + "narHash": "sha256-3ESg5ra+raxilFcmJw1vihoGS7Abet1v0OpVn1MxPzU=", "owner": "nixos", "repo": "nixpkgs", - "rev": "30a3e96da641620c63f2e1f345ea434ac78f5de1", + "rev": "69ecaffa7deb4daa5a83cb813f8251665e3af93e", "type": "github" }, "original": { @@ -981,11 +981,11 @@ }, "nixpkgs_5": { "locked": { - "lastModified": 1769972154, - "narHash": "sha256-PphDbFMES0NPP/3JCNkfNdagrDepG9rbLvJSNG0LhuI=", + "lastModified": 1770645638, + "narHash": "sha256-O0Saxnde4K+jWBkZzM+UBknFXlcCzrDXvJkTGZumEOo=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "8e4f0a16f0236fe4b52dde572fb53464bd5c7440", + "rev": "3ebfe71ecd51e74346d2cf863d0ff1d4a3ff69be", "type": "github" }, "original": { @@ -1029,11 +1029,11 @@ }, "nixpkgs_8": { "locked": { - "lastModified": 1769789167, - "narHash": "sha256-kKB3bqYJU5nzYeIROI82Ef9VtTbu4uA3YydSk/Bioa8=", + "lastModified": 1770562336, + "narHash": "sha256-ub1gpAONMFsT/GU2hV6ZWJjur8rJ6kKxdm9IlCT0j84=", "owner": "nixos", "repo": "nixpkgs", - "rev": "62c8382960464ceb98ea593cb8321a2cf8f9e3e5", + "rev": "d6c71932130818840fc8fe9509cf50be8c64634f", "type": "github" }, "original": { @@ -1094,11 +1094,11 @@ "systems": "systems_4" }, "locked": { - "lastModified": 1769915470, - "narHash": "sha256-rB851Mo2DKNfaUwfzCr9Uiya/LjRy0t7U4v1j5nDWuY=", + "lastModified": 1770572967, + "narHash": "sha256-uQ4g+gypEXoNE6bgQq1UP3mrwUNuemhdD3A7G9tbchk=", "owner": "notashelf", "repo": "nvf", - "rev": "8aae70026da3769bf16530aa513ffedeb95dd84c", + "rev": "7a2c7c23966122eac80620dd503bf2b1163ed6d4", "type": "github" }, "original": { @@ -1159,11 +1159,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1769857242, - "narHash": "sha256-3eKpRRzKz0KzY7CJzRXFm4POwEqbuTohyQ2ajI/zKvg=", + "lastModified": 1770616416, + "narHash": "sha256-S6qG5sNG76JitdRRY0dyEq9+n+4TJuqKrFrtTpripAo=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "17304e9c7e11d26139672d3d77aa498b1cae0d69", + "rev": "c75729db6845c73605115b18d819917dbf6a8972", "type": "github" }, "original": { @@ -1203,11 +1203,11 @@ ] }, "locked": { - "lastModified": 1769921679, - "narHash": "sha256-twBMKGQvaztZQxFxbZnkg7y/50BW9yjtCBWwdjtOZew=", + "lastModified": 1770526836, + "narHash": "sha256-xbvX5Ik+0inJcLJtJ/AajAt7xCk6FOCrm5ogpwwvVDg=", "owner": "Mic92", "repo": "sops-nix", - "rev": "1e89149dcfc229e7e2ae24a8030f124a31e4f24f", + "rev": "d6e0e666048a5395d6ea4283143b7c9ac704720d", "type": "github" }, "original": { @@ -1221,11 +1221,11 @@ "nixpkgs": "nixpkgs_10" }, "locked": { - "lastModified": 1769921679, - "narHash": "sha256-twBMKGQvaztZQxFxbZnkg7y/50BW9yjtCBWwdjtOZew=", + "lastModified": 1770526836, + "narHash": "sha256-xbvX5Ik+0inJcLJtJ/AajAt7xCk6FOCrm5ogpwwvVDg=", "owner": "Mic92", "repo": "sops-nix", - "rev": "1e89149dcfc229e7e2ae24a8030f124a31e4f24f", + "rev": "d6e0e666048a5395d6ea4283143b7c9ac704720d", "type": "github" }, "original": { @@ -1253,11 +1253,11 @@ "tinted-zed": "tinted-zed" }, "locked": { - "lastModified": 1769888473, - "narHash": "sha256-4KWbaJwaYnZ60bFyTudZYAKskjr7Sa17R3/yh+oXS7w=", + "lastModified": 1770587906, + "narHash": "sha256-N9ZTG3ia7l4iQO+9JlOj+sX4yu6gl7a3aozrlhSIJwQ=", "owner": "nix-community", "repo": "stylix", - "rev": "ae5c0239ae4f82a8c7e33ad8a456535d5a9ba813", + "rev": "72e6483a88d51471a6c55e1d43e7ed2bc47a76a4", "type": "github" }, "original": { @@ -1482,11 +1482,11 @@ ] }, "locked": { - "lastModified": 1769797635, - "narHash": "sha256-ENwjbVYOks9bkBAIpfwQg5uCHrU8iG7zq/5toKmdoZk=", + "lastModified": 1770584247, + "narHash": "sha256-awRLWslBvfUSreLt0IMyFYHJkvlb3roCtyMtKA47wmk=", "owner": "emmanuelrosa", "repo": "tiny_audio_player", - "rev": "350c1697d0a0181d55bda191b10a75cfcb8c9c50", + "rev": "1efef4ed191f4c9589ccd397b36feb9bc906c459", "type": "github" }, "original": { @@ -1503,11 +1503,11 @@ ] }, "locked": { - "lastModified": 1769691507, - "narHash": "sha256-8aAYwyVzSSwIhP2glDhw/G0i5+wOrren3v6WmxkVonM=", + "lastModified": 1770228511, + "narHash": "sha256-wQ6NJSuFqAEmIg2VMnLdCnUc0b7vslUohqqGGD+Fyxk=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "28b19c5844cc6e2257801d43f2772a4b4c050a1b", + "rev": "337a4fe074be1042a35086f15481d763b8ddc0e7", "type": "github" }, "original": { @@ -1524,11 +1524,11 @@ ] }, "locked": { - "lastModified": 1769922110, - "narHash": "sha256-/0Cl75Yy4mQOWNfr2ZR5aYZlFc2geH7NUkwiwiKUNhg=", + "lastModified": 1770568363, + "narHash": "sha256-RJ/C24wN7LyuMmBgvIutA/PqXXceZtJtUCuZSaTjF/4=", "owner": "0xc000022070", "repo": "zen-browser-flake", - "rev": "dc3cb779f0fae72b3ebffd60a2272095f8848eda", + "rev": "2ada8a826ea88512387a5a17ee96f16369bcdd80", "type": "github" }, "original": { From 11310fdfbd50eb3d3a902bd9e2a0d5583a440842 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Mon, 9 Feb 2026 15:13:42 +0100 Subject: [PATCH 050/108] feat: add project and app to zitadel --- systems/x86_64-linux/ulmo/default.nix | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/systems/x86_64-linux/ulmo/default.nix b/systems/x86_64-linux/ulmo/default.nix index 4203859..7440933 100644 --- a/systems/x86_64-linux/ulmo/default.nix +++ b/systems/x86_64-linux/ulmo/default.nix @@ -151,6 +151,20 @@ }; }; }; + + convex = { + projectRoleCheck = true; + projectRoleAssertion = true; + hasProjectCheck = true; + + application = { + scry = { + redirectUris = ["https://nautical-salamander-320.eu-west-1.convex.cloud/api/auth/callback/zitadel"]; + grantTypes = ["authorizationCode"]; + responseTypes = ["code"]; + }; + }; + }; }; action = { From fca97a534eca780f6c2b95d29de4a5fbd5cc6dc5 Mon Sep 17 00:00:00 2001 From: chris Date: Tue, 17 Feb 2026 13:56:26 +0000 Subject: [PATCH 051/108] chore(secrets): set secret "zitadel/users" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 646c768..13344c4 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -4,7 +4,7 @@ email: zitadel: masterKey: ENC[AES256_GCM,data:4MPvBo407qrS7NF4oUTf84tZoPkSRmiHdD7qpkYeHME=,iv:H2NIAN0xBUDqnyco9gA3zYAsKtSeA/JpqYrPhc1eqc0=,tag:6OFGDfsucG5gDerImgpuXA==,type:str] nix: {} - users: ENC[AES256_GCM,data:yxdJ2PmOJXXCF2NaD1QWLSuwF9AhdIBhLiZDm4GhcTb4sA3zGTyJBw5saH6P5QAwk9ngbOgn8RH0vgeYEJ0z8VzUoCaLWK5xaqLggYgd75ewNQu7Jkh6V/oSHeVfv+6NCRoq4PckHvhBHwQQ4uToaCghUbjX6VJlFSKwSAy6laG30UMIa2Q4hTQHqgVcbjpQUJSu6/ajDz3Ap0MqhCTSOPWKZ9vWZpvRnFhLhsJrTNl0w6zlCuZcy8xqn/zZo4OEuexHr29yFFohbiD9L9CLd0N6NYDMX7eHRjjdB6Ysxfkic9JSWysma/7OwPzg/KK+pQDkNi7ciR+/cT9Gqn73IFpXPvuooe+7wxe4INfGq3iAoRIYSz8=,iv:opqL2iB3sqT+/a03tTzWphFGnwrEwdKybnj/3BNzL3U=,tag:2+CMLgKdsWpPsYrkKAP5hg==,type:str] + users: ENC[AES256_GCM,data:6OOa6zbnCQmzBFTRanFYnCiBiWGqkzw6UE2SiGw30haADV0F7yREKXcRpa3m+Lw3nJviorleFFCWMOG0HcNiFqN0H3E85t2LOVV5BNp4wt3fDAH65XumDnuGYZmMxQxfYUFSI4T/sggrGbG+lCJw9OFzpXUjLswLLuuuRMMGtz3KxdGD5fK8q3J8WSdmjVCji/2a77z+9UulakTL6HXsILfi3kRJjrSbG9oawhMwfkU9fDlowfIAnIfkEQwu8rWz627L8Di6xx9LjOH37ca7sRlwUd0ZdXMb87U007WW9P0xLffsLcRFCpA+btOAevWFyZwnG4g3/EAkeEqCmT0bkwt9OypOv1MlC+WjlwSmdxMfO6tCtwd08BrccfMZxUml2CQ0CsmBC25+2fDnCNhSc1utl63WYLnInEkC3hJeWloOkQOxk6DK6QWt+sx2p3PZ7nwyNcyTf7OQV2DLwj389w4F,iv:TKIzEj9Prgo2PYryIXf3AMkt5FZecV3CSRg6tFIEpfo=,tag:113W71F3BA5axj+/vH+3NA==,type:str] forgejo: action_runner_token: ENC[AES256_GCM,data:yJ6OnRq5kinbuhvH06K5o3l86EafuBoojMwg/qhP+cgeH+BwPeE+Ng==,iv:IeXJahPxgLNIUFmkgp495tLVh8UyQBmJ2SnVEUhlhHs=,tag:XYQi613CxSp8AQeilJMrsg==,type:str] synapse: @@ -56,7 +56,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-12-28T21:28:31Z" - mac: ENC[AES256_GCM,data:vkGMgBkzmA2+xRIOfgUE01XG6jvTMTpm1vWXVHdZ5xE27s2mn8i6C64t1cia0n413qlKLB3y5qcbiHdRVhdLUoZFdBgFTjfixyIXOKZeVJskjJEqg2L0wZGtYIO8Y2KrfPb925qOffr7p0NcMf4c+d6bIqxHFEGb+jR/aWDOMNo=,iv:PK1FHycgOj2wtJt1UfWEAe0mKSBVksu8KWUxljSp2oo=,tag:F/xAAxJLUDqW9Dnwgrd0Rg==,type:str] + lastmodified: "2026-02-17T13:56:21Z" + mac: ENC[AES256_GCM,data:YapOj+X4b8xTIBDxVF9LV0bkKorzsP4SfDcBPizlUvxyzUkoAxSkBCaUIuKPzWnQtU9Ogd8tnPIYGxQohFmBpWn4L+z+0ERQYXReSpeQcUaC+5LgnzgEApZxz+SQhoipCXUZ0+xjs1OJ2w+znWlEoxY3umHaOZ0+WXa2R0hBsGs=,iv:L5nGv2O/KWgpRrprUAT7uvz3+nxLfEAhuj/d7l1tjUk=,tag:wUbeqssYOLjicjE9lAdeIg==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From 10a1a324cec2f2f124b8642d5254602604975506 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Tue, 17 Feb 2026 15:39:36 +0100 Subject: [PATCH 052/108] Fix table.jq to use correct index in to_line function --- .jq/table.jq | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.jq/table.jq b/.jq/table.jq index 5c58aef..8280522 100644 --- a/.jq/table.jq +++ b/.jq/table.jq @@ -24,7 +24,7 @@ def to_cells(sizes; fn): def to_cells(sizes): to_cells(sizes; null); def to_line(left; joiner; right): - [left, .[1], (.[1:] | map([joiner, .]) ), right] | flatten | join(""); + [left, .[0], (.[1:] | map([joiner, .]) ), right] | flatten | join(""); def create(data; header_callback; cell_callback): (data[0] | to_entries | map(.key)) as $keys From b72681ff6341528192d584975e5ccb2df854d9e0 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Tue, 17 Feb 2026 15:39:55 +0100 Subject: [PATCH 053/108] Update user scripts and table formatting Add doc annotations to user scripts and refine prompts for user input. Improve table.jq to use keys_unsorted for header generation. --- .jq/table.jq | 5 ++--- .just/users.just | 13 ++++++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.jq/table.jq b/.jq/table.jq index 8280522..04f80c6 100644 --- a/.jq/table.jq +++ b/.jq/table.jq @@ -27,10 +27,9 @@ def to_line(left; joiner; right): [left, .[0], (.[1:] | map([joiner, .]) ), right] | flatten | join(""); def create(data; header_callback; cell_callback): - (data[0] | to_entries | map(.key)) as $keys - | ([$keys]) as $header + (data[0] | keys_unsorted) as $keys | (data | map(to_entries | map(.value))) as $rows - | ($header + $rows) as $cells + | ([$keys] + $rows) as $cells | ( $keys # Use keys so that we have an array of the correct size | to_entries diff --git a/.just/users.just b/.just/users.just index 486ac67..0643355 100644 --- a/.just/users.just +++ b/.just/users.just @@ -2,8 +2,9 @@ set unstable := true set quiet := true _default: - just --list + just --list users +[doc('List available users')] [script] list: cd .. && just vars get ulmo zitadel/users | jq -r -C ' @@ -25,6 +26,7 @@ list: | join("\n\nβ”„β”„β”„\n\n") '; +[doc('Add a new user')] [script] add: exec 5>&1 @@ -47,10 +49,10 @@ add: jq -r 'to_entries | map(.key)[]' <<< "$data" \ | gum choose --header 'Which organisation to save to?' --select-if-one ` - username=`input 'user name' 'new-user'` - email=`input 'email' 'new.user@example.com'` - first_name=`input 'first name' 'John'` - last_name=`input 'last name' 'Doe'` + username=`input 'user name'` + email=`input 'email'` + first_name=`input 'first name'` + last_name=`input 'last name'` user_exists=`jq --arg 'org' "$org" --arg 'username' "$username" '.[$org][$username]? | . != null' <<< "$data"` @@ -72,6 +74,7 @@ add: gum spin --title "saving..." -- echo "$(cd .. && just vars set ulmo 'zitadel/users' "$next")" +[doc('Remove a new user')] [script] remove: data=`cd .. && just vars get ulmo zitadel/users | jq fromjson` From 35e608ff9aa7f4973d526abb48ffcfed367dddf7 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Tue, 17 Feb 2026 15:40:08 +0100 Subject: [PATCH 054/108] Add --build-host option to nixos-rebuild command --- .just/machine.just | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.just/machine.just b/.just/machine.just index d07986b..098d879 100644 --- a/.just/machine.just +++ b/.just/machine.just @@ -8,4 +8,4 @@ [no-exit-message] @update machine: just assert '-d "../systems/x86_64-linux/{{ machine }}"' "Machine {{ machine }} does not exist, must be one of: $(ls ../systems/x86_64-linux/ | sed ':a;N;$!ba;s/\n/, /g')" - nixos-rebuild switch -L --sudo --target-host {{ machine }} --flake ..#{{ machine }} --log-format internal-json -v |& nom --json + nixos-rebuild switch -L --sudo --target-host {{ machine }} --build-host {{ machine }} --flake ..#{{ machine }} --log-format internal-json -v |& nom --json From 7deb710db735bfd93c99a5f029e98b157a1b9373 Mon Sep 17 00:00:00 2001 From: chris Date: Tue, 17 Feb 2026 16:06:04 +0000 Subject: [PATCH 055/108] chore(secrets): set secret "zitadel/users" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 13344c4..cafcb73 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -4,7 +4,7 @@ email: zitadel: masterKey: ENC[AES256_GCM,data:4MPvBo407qrS7NF4oUTf84tZoPkSRmiHdD7qpkYeHME=,iv:H2NIAN0xBUDqnyco9gA3zYAsKtSeA/JpqYrPhc1eqc0=,tag:6OFGDfsucG5gDerImgpuXA==,type:str] nix: {} - users: ENC[AES256_GCM,data:6OOa6zbnCQmzBFTRanFYnCiBiWGqkzw6UE2SiGw30haADV0F7yREKXcRpa3m+Lw3nJviorleFFCWMOG0HcNiFqN0H3E85t2LOVV5BNp4wt3fDAH65XumDnuGYZmMxQxfYUFSI4T/sggrGbG+lCJw9OFzpXUjLswLLuuuRMMGtz3KxdGD5fK8q3J8WSdmjVCji/2a77z+9UulakTL6HXsILfi3kRJjrSbG9oawhMwfkU9fDlowfIAnIfkEQwu8rWz627L8Di6xx9LjOH37ca7sRlwUd0ZdXMb87U007WW9P0xLffsLcRFCpA+btOAevWFyZwnG4g3/EAkeEqCmT0bkwt9OypOv1MlC+WjlwSmdxMfO6tCtwd08BrccfMZxUml2CQ0CsmBC25+2fDnCNhSc1utl63WYLnInEkC3hJeWloOkQOxk6DK6QWt+sx2p3PZ7nwyNcyTf7OQV2DLwj389w4F,iv:TKIzEj9Prgo2PYryIXf3AMkt5FZecV3CSRg6tFIEpfo=,tag:113W71F3BA5axj+/vH+3NA==,type:str] + users: ENC[AES256_GCM,data:jg3G3Tvpg3ZI+TOyb+/ajgciPJUMfQ4rk0kbGI9eJ0uRTLFY0UPpGOCPx0p+AgMtD4tahxDtFAytEqh2lck3GkLRgzg8eS2/KIiM6cYl3dnd0eyWT+XoBn8cBseDux8SSL0pRIF2c8cMOlcLwMcSXnY8hzp+OB5uTx5jks5viuv7MYEoQ+ZOIuDeP+Tr+3RblwaecupopBlWZqH1o9JKLXrxkbWVAyzLd3UOuBhvxkGY9D5idDZN4F/OiIqGbjz3flwlkgHH8ta0H2QFZrQQfCDmVutbJTjeraKOGsfQpkYKD6d8lMroU8bBQa7Mkdy4b1c1KWIphER08X+DDKqdzGlrfoz/GgkFDJWvWM+FNLxu3GdNWHKlUVcK0wWlkQf8IoYCyiemFBlgv44EzKWOSfjHebKTolDBrbqAQW1A2BpAzkkdHN+FfzqeJv2du5miGkCdVNgiSoenhSx9mES8vwE=,iv:mBJJhNS856/M0nga7BlMECtFmZQEfp5c3RiAI8zqc7w=,tag:i9XKR/8emQcJsoDtmszRnA==,type:str] forgejo: action_runner_token: ENC[AES256_GCM,data:yJ6OnRq5kinbuhvH06K5o3l86EafuBoojMwg/qhP+cgeH+BwPeE+Ng==,iv:IeXJahPxgLNIUFmkgp495tLVh8UyQBmJ2SnVEUhlhHs=,tag:XYQi613CxSp8AQeilJMrsg==,type:str] synapse: @@ -56,7 +56,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-02-17T13:56:21Z" - mac: ENC[AES256_GCM,data:YapOj+X4b8xTIBDxVF9LV0bkKorzsP4SfDcBPizlUvxyzUkoAxSkBCaUIuKPzWnQtU9Ogd8tnPIYGxQohFmBpWn4L+z+0ERQYXReSpeQcUaC+5LgnzgEApZxz+SQhoipCXUZ0+xjs1OJ2w+znWlEoxY3umHaOZ0+WXa2R0hBsGs=,iv:L5nGv2O/KWgpRrprUAT7uvz3+nxLfEAhuj/d7l1tjUk=,tag:wUbeqssYOLjicjE9lAdeIg==,type:str] + lastmodified: "2026-02-17T16:06:01Z" + mac: ENC[AES256_GCM,data:LaB1gCNxzm0GmFE4x3Hq7Jx5aSytsd7ALTdGr64BvxJi/AVbkeUbgyVUtqY0BbT/toYnTmXW0pwCTwqnLAjpDFIdQE6nd8yW5jQJ/B31yV/vgR+cINqcv93lGe7AcoINzDHb/AFHFI6Zd4mMuoBaMfTl9psY32oGTH/uhgol3yo=,iv:r6dZntJLjMn5TL/A9bQay5M7P3M8oihOY7z6JPxt2rg=,tag:+S/tdMh7WEwCxJiGqc80EQ==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From 0c778497a1a64912faaff5a6671f6b1ec1b33a25 Mon Sep 17 00:00:00 2001 From: chris Date: Tue, 17 Feb 2026 16:35:02 +0000 Subject: [PATCH 056/108] chore(secrets): set secret "zitadel/users" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index cafcb73..6a65350 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -4,7 +4,7 @@ email: zitadel: masterKey: ENC[AES256_GCM,data:4MPvBo407qrS7NF4oUTf84tZoPkSRmiHdD7qpkYeHME=,iv:H2NIAN0xBUDqnyco9gA3zYAsKtSeA/JpqYrPhc1eqc0=,tag:6OFGDfsucG5gDerImgpuXA==,type:str] nix: {} - users: ENC[AES256_GCM,data:jg3G3Tvpg3ZI+TOyb+/ajgciPJUMfQ4rk0kbGI9eJ0uRTLFY0UPpGOCPx0p+AgMtD4tahxDtFAytEqh2lck3GkLRgzg8eS2/KIiM6cYl3dnd0eyWT+XoBn8cBseDux8SSL0pRIF2c8cMOlcLwMcSXnY8hzp+OB5uTx5jks5viuv7MYEoQ+ZOIuDeP+Tr+3RblwaecupopBlWZqH1o9JKLXrxkbWVAyzLd3UOuBhvxkGY9D5idDZN4F/OiIqGbjz3flwlkgHH8ta0H2QFZrQQfCDmVutbJTjeraKOGsfQpkYKD6d8lMroU8bBQa7Mkdy4b1c1KWIphER08X+DDKqdzGlrfoz/GgkFDJWvWM+FNLxu3GdNWHKlUVcK0wWlkQf8IoYCyiemFBlgv44EzKWOSfjHebKTolDBrbqAQW1A2BpAzkkdHN+FfzqeJv2du5miGkCdVNgiSoenhSx9mES8vwE=,iv:mBJJhNS856/M0nga7BlMECtFmZQEfp5c3RiAI8zqc7w=,tag:i9XKR/8emQcJsoDtmszRnA==,type:str] + users: ENC[AES256_GCM,data:GeuYaMNy5UICpYsJs95YCzKjtZxMQDpTTKjU9BDTTBToFir9Rn04QxNi6Wj1I3h2FcS63kbHWaP/W307kDoOQPBZrvrQMi2zSc8+/ivYGmAMhzt2kYXECKt7+YDf9cn9f3D6KV0NSh5UqlpZvDfYXSc/6q2gHOMV3bPvMEf+uxxUEf06kzLH7HpnjExgmUcOM6uVNAudf7HgJ1h/JzUzHq/XLPoVAuyj2TbaHwvRCk+YqLJZw1D+8z3MkpZsdNF57quEZfDPahg371l1wzvZMH5sfFuSzUajD3lbIGRiV0o9PpmBp4qwnU63EkFy1wqhMjXjoviPqouRyZb+fDzxkDoyMuhAYjHcIuSlAs3/5htO2CzOCQL1t6cfEGaxG5U4ocHH0b0C5wvqNG5u9NDLbP7AjBS3a9TLPfmjijNBd4xwt/eYCpDkXirv/m/PVQLvbNzzf+zDWDApldxmv5rCxTLR,iv:FqShjMhe8Yzh2RiC991mVhATYZD2rdW3m0js9gQsKC4=,tag:8P7nEFVVY32IFP9C5H4cZA==,type:str] forgejo: action_runner_token: ENC[AES256_GCM,data:yJ6OnRq5kinbuhvH06K5o3l86EafuBoojMwg/qhP+cgeH+BwPeE+Ng==,iv:IeXJahPxgLNIUFmkgp495tLVh8UyQBmJ2SnVEUhlhHs=,tag:XYQi613CxSp8AQeilJMrsg==,type:str] synapse: @@ -56,7 +56,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-02-17T16:06:01Z" - mac: ENC[AES256_GCM,data:LaB1gCNxzm0GmFE4x3Hq7Jx5aSytsd7ALTdGr64BvxJi/AVbkeUbgyVUtqY0BbT/toYnTmXW0pwCTwqnLAjpDFIdQE6nd8yW5jQJ/B31yV/vgR+cINqcv93lGe7AcoINzDHb/AFHFI6Zd4mMuoBaMfTl9psY32oGTH/uhgol3yo=,iv:r6dZntJLjMn5TL/A9bQay5M7P3M8oihOY7z6JPxt2rg=,tag:+S/tdMh7WEwCxJiGqc80EQ==,type:str] + lastmodified: "2026-02-17T16:34:59Z" + mac: ENC[AES256_GCM,data:oUH+7PZgre09Dse/I4OdS8Wizyu1tt4m7NDnPjFRCgz1t1vAawMxK+zLjHi/rM5A6BobPlPLIvPIvizwVID2Hvu7gZXZ98HujTa5Z9oUP1DVsCXi1nDYOg5mXSdAo78AVsdXonoazeQUpAgI+X49E/wqhpE21Gg5MPcYokg+N54=,iv:hC9mWpCuXRbpLLC2j73kcYY6P1DgiLUxVZHYCNiPiO0=,tag:F6AEVHXpG7JfP7MHrIw9mg==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From 625e79f0426738bd9e4710837af015b28edfb259 Mon Sep 17 00:00:00 2001 From: chris Date: Mon, 23 Feb 2026 07:16:15 +0000 Subject: [PATCH 057/108] chore: update dependencies --- flake.lock | 188 ++++++++++++++++++++++++++--------------------------- 1 file changed, 94 insertions(+), 94 deletions(-) diff --git a/flake.lock b/flake.lock index ae2abb3..5faa0c0 100644 --- a/flake.lock +++ b/flake.lock @@ -83,11 +83,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1770634898, - "narHash": "sha256-v05MDDR9Sv8adHsMTNoCHOy4DH5nqOtvaGMeKk4sC4s=", - "rev": "5edc04cc6a1183ad85322deed75a9d4824f7e9f7", + "lastModified": 1771802456, + "narHash": "sha256-Ku3vdfRr0JBcTbcu8oNSVYNLLDVrIlDXvuYv0qZaJvg=", + "rev": "e1f0211652ba266dc0ca504fe3c4775d8cad16f8", "type": "tarball", - "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/5edc04cc6a1183ad85322deed75a9d4824f7e9f7.tar.gz" + "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/e1f0211652ba266dc0ca504fe3c4775d8cad16f8.tar.gz" }, "original": { "type": "tarball", @@ -125,11 +125,11 @@ ] }, "locked": { - "lastModified": 1770409579, - "narHash": "sha256-reWzIb3dxJnLcwBEuT6khzEDvCiBCVTiqBR9C4vH/jg=", - "rev": "5065ddc67a7009fb81a29f43aa056b2a4552ed96", + "lastModified": 1771586574, + "narHash": "sha256-Nzay8rHhCrlFaIiDqlTpEiKZZTUOQsdZJ8wdB+lrJro=", + "rev": "17da134c02b2e92e10ffcbcb4870e5cde0a6c6f7", "type": "tarball", - "url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/5065ddc67a7009fb81a29f43aa056b2a4552ed96.tar.gz" + "url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/17da134c02b2e92e10ffcbcb4870e5cde0a6c6f7.tar.gz" }, "original": { "type": "tarball", @@ -144,11 +144,11 @@ ] }, "locked": { - "lastModified": 1769524058, - "narHash": "sha256-zygdD6X1PcVNR2PsyK4ptzrVEiAdbMqLos7utrMDEWE=", + "lastModified": 1771469470, + "narHash": "sha256-GnqdqhrguKNN3HtVfl6z+zbV9R9jhHFm3Z8nu7R6ml0=", "owner": "nix-community", "repo": "disko", - "rev": "71a3fc97d80881e91710fe721f1158d3b96ae14d", + "rev": "4707eec8d1d2db5182ea06ed48c820a86a42dc13", "type": "github" }, "original": { @@ -164,11 +164,11 @@ "tiny-audio-player": "tiny-audio-player" }, "locked": { - "lastModified": 1770584961, - "narHash": "sha256-5/ZAb9j1ih+14Ma34iNOgotA3BjQpayqg1O9+e2d7jU=", + "lastModified": 1771529616, + "narHash": "sha256-FiVKf4ZSHCcHOKkQAaIcjQGWiTnlepv5462Djk10BeY=", "owner": "emmanuelrosa", "repo": "erosanix", - "rev": "394debf46a32b883dc572472cbda18482eb2de92", + "rev": "ed5217725bf19acfb594be8a4a653e3f576a3397", "type": "github" }, "original": { @@ -185,11 +185,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1770621632, - "narHash": "sha256-pp7visGpp5SYL1O/eF1ZyiSqk4AJ5xkEJXw7pw0f4EI=", + "lastModified": 1771743970, + "narHash": "sha256-eri4eY0fUouYxBgWxJAJzG+xTGXVI7VeNJGcJrqpEt0=", "owner": "nix-community", "repo": "fenix", - "rev": "de681afb16166786926b05a0b528545ad511507a", + "rev": "2af8ae8bbe91833a54bd3b9cc24c326b66972a8e", "type": "github" }, "original": { @@ -205,11 +205,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1770642320, - "narHash": "sha256-CgL4Y8mdt7ty4uxp4NfUXKhrSar6TMUtCgmh0M16JGo=", + "lastModified": 1771811831, + "narHash": "sha256-adtW0jeSg/uZ6anL1mhK+kHAPpYR1+X5kmL6ZtDrQkw=", "owner": "nix-community", "repo": "flake-firefox-nightly", - "rev": "aef5ff9c6122e50fcef4e06d73435cb6cbfbc888", + "rev": "0cd9d065adab3b7d12747ba54cbf0e9b4154351f", "type": "github" }, "original": { @@ -363,11 +363,11 @@ ] }, "locked": { - "lastModified": 1768135262, - "narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=", + "lastModified": 1769996383, + "narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac", + "rev": "57928607ea566b5db3ad13af0e57e921e6b12381", "type": "github" }, "original": { @@ -552,11 +552,11 @@ ] }, "locked": { - "lastModified": 1770420941, - "narHash": "sha256-aWgduwwaVAgdbGInybYpD7zWY1WXs1ZM7vQkkpfWKyk=", + "lastModified": 1771538633, + "narHash": "sha256-MBA5xFLd4dXdNwCYintpO7yBm2xj92PagsNmYpw+tSg=", "owner": "himmelblau-idm", "repo": "himmelblau", - "rev": "a6a15bc28852010c2aaa643991123d0e4ab06692", + "rev": "a734234d38833fc4d0522e79e308daf99bd5f1e1", "type": "github" }, "original": { @@ -572,11 +572,11 @@ ] }, "locked": { - "lastModified": 1770642890, - "narHash": "sha256-XWWHZEy5ZYMOx5hVuz+oeKtKDfv7syl7dwKCBx0LqzA=", + "lastModified": 1771756436, + "narHash": "sha256-Tl2I0YXdhSTufGqAaD1ySh8x+cvVsEI1mJyJg12lxhI=", "owner": "nix-community", "repo": "home-manager", - "rev": "13a1beb7c9962e0d2ba35a4d5c87546509b89b7d", + "rev": "5bd3589390b431a63072868a90c0f24771ff4cbb", "type": "github" }, "original": { @@ -593,11 +593,11 @@ ] }, "locked": { - "lastModified": 1769872935, - "narHash": "sha256-07HMIGQ/WJeAQJooA7Kkg1SDKxhAiV6eodvOwTX6WKI=", + "lastModified": 1771756436, + "narHash": "sha256-Tl2I0YXdhSTufGqAaD1ySh8x+cvVsEI1mJyJg12lxhI=", "owner": "nix-community", "repo": "home-manager", - "rev": "f4ad5068ee8e89e4a7c2e963e10dd35cd77b37b7", + "rev": "5bd3589390b431a63072868a90c0f24771ff4cbb", "type": "github" }, "original": { @@ -614,11 +614,11 @@ ] }, "locked": { - "lastModified": 1770620462, - "narHash": "sha256-6oT0qd5nRpn+smwnUWgiYgN8+PCyNxjRCiaWkqlijAc=", + "lastModified": 1771587792, + "narHash": "sha256-XGFLdlLOez7f0rmjlF+1TLXyBguy8gx2aBHx/Q5JXxs=", "owner": "Jovian-Experiments", "repo": "Jovian-NixOS", - "rev": "d58e0f3603bca6caf57aff6bf3c7705004e46f93", + "rev": "b49fc54950e251f166a2240799315033ab7a8916", "type": "github" }, "original": { @@ -633,11 +633,11 @@ "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1770555544, - "narHash": "sha256-ebtYu7XDrNMKgQ1ZStwHbD53uofYKVZudhuvMCXR3NA=", + "lastModified": 1771765102, + "narHash": "sha256-RLvOaBEoxgPnGZn9ULbb6xXs98AgiOyPZQpB44XyLvA=", "owner": "nix-community", "repo": "lib-aggregate", - "rev": "724c7b7f76794102b60482233a0e226056ca5b0c", + "rev": "55efa4ba1ddbbe046a4afd17b51867c5348bdce8", "type": "github" }, "original": { @@ -667,11 +667,11 @@ }, "mnw": { "locked": { - "lastModified": 1769981889, - "narHash": "sha256-ndI7AxL/6auelkLHngdUGVImBiHkG8w2N2fOTKZKn4k=", + "lastModified": 1770419553, + "narHash": "sha256-b1XqsH7AtVf2dXmq2iyRr2NC1yG7skY7Z6N2MpWHlK4=", "owner": "Gerg-L", "repo": "mnw", - "rev": "332fed8f43b77149c582f1782683d6aeee1f07cf", + "rev": "2aaffa8030d0b262176146adbb6b0e6374ce2957", "type": "github" }, "original": { @@ -729,11 +729,11 @@ ] }, "locked": { - "lastModified": 1770184146, - "narHash": "sha256-DsqnN6LvXmohTRaal7tVZO/AKBuZ02kPBiZKSU4qa/k=", + "lastModified": 1771520882, + "narHash": "sha256-9SeTZ4Pwr730YfT7V8Azb8GFbwk1ZwiQDAwft3qAD+o=", "owner": "nix-darwin", "repo": "nix-darwin", - "rev": "0d7874ef7e3ba02d58bebb871e6e29da36fa1b37", + "rev": "6a7fdcd5839ec8b135821179eea3b58092171bcf", "type": "github" }, "original": { @@ -771,11 +771,11 @@ "systems": "systems_3" }, "locked": { - "lastModified": 1770520993, - "narHash": "sha256-ks1ZFBYlBmQ4CAM4WSmCFUtkUJzbmJ0VJH/JkKVMPqY=", + "lastModified": 1771641457, + "narHash": "sha256-TIekRGfeCwuEmYcWex40RTx0Gd46pqmyUtxdFKb5juI=", "owner": "Infinidoge", "repo": "nix-minecraft", - "rev": "b32f4325880b4fac47b8736161a8f032dd248b70", + "rev": "c4e2b8969e09067da9d44b6b5762e1e896418f40", "type": "github" }, "original": { @@ -856,11 +856,11 @@ ] }, "locked": { - "lastModified": 1770645108, - "narHash": "sha256-j19Q1HZNfMxoG1WOGFUF1HPZ/wHkVlLDqjvNhrq5frA=", + "lastModified": 1771563879, + "narHash": "sha256-vA5hocvdGhr+jfBN7A7ogeZqIz2qx01EixXwdVsQcnE=", "owner": "nix-community", "repo": "nixos-wsl", - "rev": "7cfb408f6a5cd243a727aa3397f4c04f5bfccf28", + "rev": "379d20c55f552e91fb9f3f0382e4a97d3f452943", "type": "github" }, "original": { @@ -887,11 +887,11 @@ }, "nixpkgs-lib": { "locked": { - "lastModified": 1770515899, - "narHash": "sha256-hbmM5OSFCXIyoYvmZyQL9mjQ2mh/L1+2/4gf/BpXWNE=", + "lastModified": 1771723719, + "narHash": "sha256-e+/T/pmEkLP6BHhYjx6GmwP5ivonQQn0bJdH9YrRB+Q=", "owner": "nix-community", "repo": "nixpkgs.lib", - "rev": "fd928847a8e03461e4b37699e6218539b610217d", + "rev": "36b8fcb216736b0e1869740b324ae521e5df23d8", "type": "github" }, "original": { @@ -902,11 +902,11 @@ }, "nixpkgs_10": { "locked": { - "lastModified": 1770380644, - "narHash": "sha256-P7dWMHRUWG5m4G+06jDyThXO7kwSk46C1kgjEWcybkE=", + "lastModified": 1771207753, + "narHash": "sha256-b9uG8yN50DRQ6A7JdZBfzq718ryYrlmGgqkRm9OOwCE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ae67888ff7ef9dff69b3cf0cc0fbfbcd3a722abe", + "rev": "d1c15b7d5806069da59e819999d70e1cec0760bf", "type": "github" }, "original": { @@ -934,11 +934,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1770625969, - "narHash": "sha256-3ESg5ra+raxilFcmJw1vihoGS7Abet1v0OpVn1MxPzU=", + "lastModified": 1771742218, + "narHash": "sha256-ofVOq6pFrLkIE6YanvUDElZJRwjSSJaTuilqhdnatMA=", "owner": "nixos", "repo": "nixpkgs", - "rev": "69ecaffa7deb4daa5a83cb813f8251665e3af93e", + "rev": "aaf43e7c58bb8093a6325ef1d7b4af616779abc5", "type": "github" }, "original": { @@ -981,11 +981,11 @@ }, "nixpkgs_5": { "locked": { - "lastModified": 1770645638, - "narHash": "sha256-O0Saxnde4K+jWBkZzM+UBknFXlcCzrDXvJkTGZumEOo=", + "lastModified": 1771829251, + "narHash": "sha256-aCGm04/IRKKAy9qzvSOjSOkcYmNEjaoClo/9FygDp2Y=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "3ebfe71ecd51e74346d2cf863d0ff1d4a3ff69be", + "rev": "cb31c55b2ba66c33f94d251251c37802ff5b1dab", "type": "github" }, "original": { @@ -1029,11 +1029,11 @@ }, "nixpkgs_8": { "locked": { - "lastModified": 1770562336, - "narHash": "sha256-ub1gpAONMFsT/GU2hV6ZWJjur8rJ6kKxdm9IlCT0j84=", + "lastModified": 1771369470, + "narHash": "sha256-0NBlEBKkN3lufyvFegY4TYv5mCNHbi5OmBDrzihbBMQ=", "owner": "nixos", "repo": "nixpkgs", - "rev": "d6c71932130818840fc8fe9509cf50be8c64634f", + "rev": "0182a361324364ae3f436a63005877674cf45efb", "type": "github" }, "original": { @@ -1045,11 +1045,11 @@ }, "nixpkgs_9": { "locked": { - "lastModified": 1769461804, - "narHash": "sha256-msG8SU5WsBUfVVa/9RPLaymvi5bI8edTavbIq3vRlhI=", + "lastModified": 1771008912, + "narHash": "sha256-gf2AmWVTs8lEq7z/3ZAsgnZDhWIckkb+ZnAo5RzSxJg=", "owner": "nixos", "repo": "nixpkgs", - "rev": "bfc1b8a4574108ceef22f02bafcf6611380c100d", + "rev": "a82ccc39b39b621151d6732718e3e250109076fa", "type": "github" }, "original": { @@ -1094,11 +1094,11 @@ "systems": "systems_4" }, "locked": { - "lastModified": 1770572967, - "narHash": "sha256-uQ4g+gypEXoNE6bgQq1UP3mrwUNuemhdD3A7G9tbchk=", + "lastModified": 1771704400, + "narHash": "sha256-8U9xnN4HdxPfAXAft3lBsArWSv1ZTTxJci1lOA/xpno=", "owner": "notashelf", "repo": "nvf", - "rev": "7a2c7c23966122eac80620dd503bf2b1163ed6d4", + "rev": "5c38b357da7e8c870350cd1847fb5b2602a28eb0", "type": "github" }, "original": { @@ -1117,11 +1117,11 @@ ] }, "locked": { - "lastModified": 1769956244, + "lastModified": 1770766818, "narHash": "sha256-12RCFLyAedyMOdenUi7cN3ioJPEGjA/ZG1BLjugfUVs=", "owner": "nix-community", "repo": "plasma-manager", - "rev": "fe54ea85c6e4413fba03b84d50f2b431d2f7c831", + "rev": "44b928068359b7d2310a34de39555c63c93a2c90", "type": "github" }, "original": { @@ -1159,11 +1159,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1770616416, - "narHash": "sha256-S6qG5sNG76JitdRRY0dyEq9+n+4TJuqKrFrtTpripAo=", + "lastModified": 1771639390, + "narHash": "sha256-igbphgls7JmrblWCIbgBGcL/ZWj0Iv+InySvuhLC5Ew=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "c75729db6845c73605115b18d819917dbf6a8972", + "rev": "af68fc6e782f218c262a8e7e5718ce7276f697a2", "type": "github" }, "original": { @@ -1203,11 +1203,11 @@ ] }, "locked": { - "lastModified": 1770526836, - "narHash": "sha256-xbvX5Ik+0inJcLJtJ/AajAt7xCk6FOCrm5ogpwwvVDg=", + "lastModified": 1771735105, + "narHash": "sha256-MJuVJeszZEziquykEHh/hmgIHYxUcuoG/1aowpLiSeU=", "owner": "Mic92", "repo": "sops-nix", - "rev": "d6e0e666048a5395d6ea4283143b7c9ac704720d", + "rev": "d7755d820f5fa8acf7f223309c33e25d4f92e74f", "type": "github" }, "original": { @@ -1221,11 +1221,11 @@ "nixpkgs": "nixpkgs_10" }, "locked": { - "lastModified": 1770526836, - "narHash": "sha256-xbvX5Ik+0inJcLJtJ/AajAt7xCk6FOCrm5ogpwwvVDg=", + "lastModified": 1771735105, + "narHash": "sha256-MJuVJeszZEziquykEHh/hmgIHYxUcuoG/1aowpLiSeU=", "owner": "Mic92", "repo": "sops-nix", - "rev": "d6e0e666048a5395d6ea4283143b7c9ac704720d", + "rev": "d7755d820f5fa8acf7f223309c33e25d4f92e74f", "type": "github" }, "original": { @@ -1253,11 +1253,11 @@ "tinted-zed": "tinted-zed" }, "locked": { - "lastModified": 1770587906, - "narHash": "sha256-N9ZTG3ia7l4iQO+9JlOj+sX4yu6gl7a3aozrlhSIJwQ=", + "lastModified": 1771787992, + "narHash": "sha256-Vg4bGwwenNYI8p3nJTl9FRyeIyrjATeZrZr+GyUSDrw=", "owner": "nix-community", "repo": "stylix", - "rev": "72e6483a88d51471a6c55e1d43e7ed2bc47a76a4", + "rev": "30054cca073b49b42a71289edec858f535b27fe9", "type": "github" }, "original": { @@ -1380,11 +1380,11 @@ "systems": "systems_7" }, "locked": { - "lastModified": 1762472226, - "narHash": "sha256-iVS4sxVgGn+T74rGJjEJbzx+kjsuaP3wdQVXBNJ79A0=", + "lastModified": 1771504637, + "narHash": "sha256-qPYBCcvws0cqVf4blYyxQ6JNxOdvUPK41s2sfqk6wL0=", "owner": "terranix", "repo": "terranix", - "rev": "3b5947a48da5694094b301a3b1ef7b22ec8b19fc", + "rev": "f3d77064bd135823a30916a1e63b90b7fe4453ac", "type": "github" }, "original": { @@ -1482,11 +1482,11 @@ ] }, "locked": { - "lastModified": 1770584247, - "narHash": "sha256-awRLWslBvfUSreLt0IMyFYHJkvlb3roCtyMtKA47wmk=", + "lastModified": 1771529133, + "narHash": "sha256-nnd13UkxEGBNCJUpSinNyoDfB1BjhSGnWN8llDM9AW8=", "owner": "emmanuelrosa", "repo": "tiny_audio_player", - "rev": "1efef4ed191f4c9589ccd397b36feb9bc906c459", + "rev": "21b191dce6be77dcf0f5baa69564b7e33905c653", "type": "github" }, "original": { @@ -1524,11 +1524,11 @@ ] }, "locked": { - "lastModified": 1770568363, - "narHash": "sha256-RJ/C24wN7LyuMmBgvIutA/PqXXceZtJtUCuZSaTjF/4=", + "lastModified": 1771829403, + "narHash": "sha256-y6SCyTHx3mfeJphVAP9IcYwmd81l7Owv1WObibVcexw=", "owner": "0xc000022070", "repo": "zen-browser-flake", - "rev": "2ada8a826ea88512387a5a17ee96f16369bcdd80", + "rev": "16e6705c152f28f380aac601c705fbe905a58b44", "type": "github" }, "original": { From e1614dc3f7c528e35bdaf82fa80623044117dc92 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Mon, 23 Feb 2026 08:17:01 +0100 Subject: [PATCH 058/108] Fix formatting in Zitadel and PostgreSQL Nix modules --- .../services/authentication/zitadel/default.nix | 3 +-- .../services/persistance/postgesql/default.nix | 13 +++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/modules/nixos/services/authentication/zitadel/default.nix b/modules/nixos/services/authentication/zitadel/default.nix index c0d9dc5..aaf64f6 100644 --- a/modules/nixos/services/authentication/zitadel/default.nix +++ b/modules/nixos/services/authentication/zitadel/default.nix @@ -444,8 +444,7 @@ in |> withRef "org" org |> toResource "${org}_${name}" ) - |> append - [ + |> append [ (forEach "local.extra_users" [ "org" "name" ] { orgId = lib.tfRef "local.orgs[each.value.org]"; userName = lib.tfRef "each.value.name"; diff --git a/modules/nixos/services/persistance/postgesql/default.nix b/modules/nixos/services/persistance/postgesql/default.nix index dbd6604..403c07c 100644 --- a/modules/nixos/services/persistance/postgesql/default.nix +++ b/modules/nixos/services/persistance/postgesql/default.nix @@ -1,14 +1,19 @@ -{ config, lib, pkgs, namespace, ... }: -let +{ + config, + lib, + pkgs, + namespace, + ... +}: let inherit (lib) mkIf mkEnableOption; cfg = config.${namespace}.services.persistance.postgresql; -in -{ +in { options.${namespace}.services.persistance.postgresql = { enable = mkEnableOption "Postgresql"; }; + # Access db with `psql -U postgres` config = mkIf cfg.enable { services = { postgresql = { From eed7d360c872d48069fa431cf9ef44d123185b25 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Mon, 23 Feb 2026 08:17:09 +0100 Subject: [PATCH 059/108] Add default value argument to input prompts in add recipe --- .just/users.just | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.just/users.just b/.just/users.just index 0643355..e798cc3 100644 --- a/.just/users.just +++ b/.just/users.just @@ -49,10 +49,10 @@ add: jq -r 'to_entries | map(.key)[]' <<< "$data" \ | gum choose --header 'Which organisation to save to?' --select-if-one ` - username=`input 'user name'` - email=`input 'email'` - first_name=`input 'first name'` - last_name=`input 'last name'` + username=`input 'user name' ''` + email=`input 'email' ''` + first_name=`input 'first name' ''` + last_name=`input 'last name' ''` user_exists=`jq --arg 'org' "$org" --arg 'username' "$username" '.[$org][$username]? | . != null' <<< "$data"` From f98cc52d62e0f8d15ae5dca3f70cf2914f360a29 Mon Sep 17 00:00:00 2001 From: chris Date: Mon, 23 Feb 2026 07:25:55 +0000 Subject: [PATCH 060/108] chore(secrets): set secret "grafana/secret_key" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 6a65350..c27c640 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -29,6 +29,7 @@ qbittorrent: grafana: oidc_id: ENC[AES256_GCM,data:NVdIgCQ6nz4BSUDJYCKyILtK,iv:tcljy9PzC/yyd7TSdngyJt+uh60uXi2PKu47czErbaQ=,tag:zE4q3dD4UQaHIpGeZ1L48Q==,type:str] oidc_secret: ENC[AES256_GCM,data:b7qILK9ZHW2khtM1Hl/KdjCv3Wq6eOo2Ym/cbjcMB8/3Hn2UelpP4K4lFyiV3bn1/GF6Jl5Z7A0EwMybOx0InA==,iv:3HL/7BiyObwT8DmFxzNPI9CdmCH/4j/4oc9x7qBE1k0=,tag:dBhcq1zLKy6N+jp/v42R4A==,type:str] + secret_key: ENC[AES256_GCM,data:u6IRFV1D/4g+eqQIUPW0QHlkoa+MliymThp34k+QCHqQ247er4bCdgftuWsXgPAPY7DtwFVLG7Do5eBqIiii7g==,iv:FY7LIW0O5/Cp2JvYu17ctInt0rgkzjaPHfxZBs0GTac=,tag:Gtu+ZGAgsi5vzILOKDac1g==,type:str] sabnzbd: sunnyweb: password: ENC[AES256_GCM,data:flw8AahqO1Mx,iv:Qhu8iVWMzzqy18y8dj3aHoBnSZatm74/tYvZ456l2sA=,tag:sCYBdw7kD0zJZFFr5EyPIQ==,type:str] @@ -56,7 +57,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-02-17T16:34:59Z" - mac: ENC[AES256_GCM,data:oUH+7PZgre09Dse/I4OdS8Wizyu1tt4m7NDnPjFRCgz1t1vAawMxK+zLjHi/rM5A6BobPlPLIvPIvizwVID2Hvu7gZXZ98HujTa5Z9oUP1DVsCXi1nDYOg5mXSdAo78AVsdXonoazeQUpAgI+X49E/wqhpE21Gg5MPcYokg+N54=,iv:hC9mWpCuXRbpLLC2j73kcYY6P1DgiLUxVZHYCNiPiO0=,tag:F6AEVHXpG7JfP7MHrIw9mg==,type:str] + lastmodified: "2026-02-23T07:25:54Z" + mac: ENC[AES256_GCM,data:6RAfiLmHjCIy48cQzDwsGImn0YgY/eer7XnshHj0w93SQ+i04X9zYRQHgAZqteU8/8BAcCote3YAdoUmNVRhO+HvXKiVKx7RC0KGgmZmKml4LaY9BnETJisPS1Z9O/k56x8sDsuI75zFeXs58DVvH7OgnkR9O8VVSpsUgnaXoEc=,iv:s0jnDITclgwKGB7t06qjzvVRI9jbZgcQJyVSLnHJ0nA=,tag:W68wmQaxzA/Ra7NMM6XDtQ==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From 6fde383844f9268f5a0a8eee896df451bf92fdaa Mon Sep 17 00:00:00 2001 From: chris Date: Tue, 24 Feb 2026 07:24:45 +0000 Subject: [PATCH 061/108] chore(secrets): set secret "coturn/secret" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index c27c640..10fa4c9 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -37,6 +37,8 @@ sabnzbd: apikey: ENC[AES256_GCM,data:j5sPXKbBhMdNHOuoTfZ+c8nGu5JameOgK2z428iLdP01Hi6MvHVaN8Zs8YxMoSBtOjdtIEC8MS+3m1S1rU/P4pCRfZpK5ua1DBHq4l0xROUqokFWjDcAmJJv3pYXl0cQxQcGKQ==,iv:v5hu3gmO1Zn1FfXkHLPGN9f7JOcQjzoQahdqJwfM+xY=,tag:uI1LFcTgcyRgAaTJ1kzKow==,type:str] whisparr: apikey: ENC[AES256_GCM,data:kIGCsd4mszm90PoQMzlSEBKw9Ow0GvP1qdLtwXYKkAb6b65l89v8lMWJ2X1MyD2gJX+P+Bv1F/2BSjUFXErq/UYnp4dAjwKi/ezGCbhjMutDM1FvwFWEHRnR3gjd9uXPWJ8Xhg==,iv:98aPQlcZHJovpnzACDs6RtKblLnHg6wyi+Er5DAowj8=,tag:Tl8jz/pWYWAtBCfoztKdyw==,type:str] +coturn: + secret: ENC[AES256_GCM,data:5RmLZ7vQIAvIzvax8oNJkImQ6vXR+MZ2eqxaBJCBlccnFC1rP16/6UtausXVf0eWysw+fpMW5yEmUtAdyxQoPiBCK8lziAZBdkekQnAvFouBaWy8WIZt6XRa71P4xDCDGudpMiGwGGNt+R9yylez+azaLrLyJM3481RPohDMoOM=,iv:2P83lgxGtHwYr+ApAdHopVfRWagxWlC+nt53API/SiQ=,tag:Qv+A03BE1QvEqJMtORiQVA==,type:str] sops: age: - recipient: age19qfpf980tadguqq44zf6xwvjvl428dyrj46ha3n6aeqddwhtnuqqml7etq @@ -57,7 +59,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-02-23T07:25:54Z" - mac: ENC[AES256_GCM,data:6RAfiLmHjCIy48cQzDwsGImn0YgY/eer7XnshHj0w93SQ+i04X9zYRQHgAZqteU8/8BAcCote3YAdoUmNVRhO+HvXKiVKx7RC0KGgmZmKml4LaY9BnETJisPS1Z9O/k56x8sDsuI75zFeXs58DVvH7OgnkR9O8VVSpsUgnaXoEc=,iv:s0jnDITclgwKGB7t06qjzvVRI9jbZgcQJyVSLnHJ0nA=,tag:W68wmQaxzA/Ra7NMM6XDtQ==,type:str] + lastmodified: "2026-02-24T07:24:41Z" + mac: ENC[AES256_GCM,data:wmTua89j8OYC4lw5nmDgKQy2A31KbI5M8jQxqNicHUEZFnDjo2aloNrpwKz5/lM5EomPvvJEAm2eyV04EmfYqKqmAXbcj2wTl4f6Afzb1X/+uABCvlaHquTXbx6lU8IyA31nHKeBspRX0mED86wrXasOsG34YJdDTa/lAKymjzk=,iv:Qea6St+3XfoVHAuWczK/rF2lEeKQBguRGpfGybdf7lA=,tag:yFufTgb7AG5dhrQH47Y0tA==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From f3e58541207a54fe4d836c70db3f0fd919f9ecc5 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Tue, 24 Feb 2026 15:55:08 +0100 Subject: [PATCH 062/108] start on poor man's clan vars --- .just/vars.just | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/.just/vars.just b/.just/vars.just index 230f00c..1ddfbfd 100644 --- a/.just/vars.just +++ b/.just/vars.just @@ -4,15 +4,17 @@ set quiet := true base_path := invocation_directory() / "systems/x86_64-linux" _default: - just --list + just --list vars -[doc('list all vars of the target machine')] +[doc('List all vars of {machine}')] list machine: sops decrypt {{ base_path }}/{{ machine }}/secrets.yml +[doc('Edit all vars of {machine} in your editor')] edit machine: sops edit {{ base_path }}/{{ machine }}/secrets.yml +[doc('Set var {value} by {key} for {machine}')] @set machine key value: sops set {{ base_path }}/{{ machine }}/secrets.yml "$(printf '%s\n' '["{{ key }}"]' | sed -E 's#/#"]["#g; s/\["([0-9]+)"\]/[\1]/g')" "\"$(echo '{{ value }}' | sed 's/\"/\\\"/g')\"" @@ -21,9 +23,11 @@ edit machine: echo "Done" +[doc('Get var value by {key} of {machine}')] get machine key: sops decrypt {{ base_path }}/{{ machine }}/secrets.yml | yq ".$(echo "{{ key }}" | sed -E 's/\//./g')" +[doc('Remove var by {key} for {machine}')] remove machine key: sops unset {{ base_path }}/{{ machine }}/secrets.yml "$(printf '%s\n' '["{{ key }}"]' | sed -E 's#/#"]["#g; s/\["([0-9]+)"\]/[\1]/g')" @@ -31,3 +35,28 @@ remove machine key: git commit -m 'chore(secrets): removed secret "{{ key }}" from machine "{{ machine }}"' -- {{ base_path }}/{{ machine }}/secrets.yml > /dev/null echo "Done" + +[script] +check: + for machine in $(ls {{ base_path }}); do + [ -f "{{ base_path }}/$machine/secrets.yml" ] || continue + [ -f "{{ base_path }}/$machine/default.nix" ] || continue + + echo "Processing $machine" + + mapfile -t missing < <(jq -nr \ + --rawfile defined <(nix eval --json --apply 'builtins.attrNames' ..#nixosConfigurations.$machine.config.sops.secrets 2>/dev/null) \ + --rawfile configured <(sops decrypt {{ base_path }}/$machine/secrets.yml | yq '.') \ + ' + $defined | fromjson as $def + | $configured + | fromjson + | paths(scalars) + | join("/") + | select(. | IN($def[]) | not) + ') + + if (( ${#missing[@]} > 0 )); then + printf 'missing the following %d secret(s):\n%s\n\n' "${#missing[@]}" "$(printf -- '- %s\n' "${missing[@]}")" + fi + done From 6d1bd782a85f6bb62ae7ed7f554beaf8433d010c Mon Sep 17 00:00:00 2001 From: chris Date: Wed, 25 Feb 2026 07:35:42 +0000 Subject: [PATCH 063/108] chore(secrets): set secret "zitadel/users" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 10fa4c9..729bed1 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -4,7 +4,7 @@ email: zitadel: masterKey: ENC[AES256_GCM,data:4MPvBo407qrS7NF4oUTf84tZoPkSRmiHdD7qpkYeHME=,iv:H2NIAN0xBUDqnyco9gA3zYAsKtSeA/JpqYrPhc1eqc0=,tag:6OFGDfsucG5gDerImgpuXA==,type:str] nix: {} - users: ENC[AES256_GCM,data:GeuYaMNy5UICpYsJs95YCzKjtZxMQDpTTKjU9BDTTBToFir9Rn04QxNi6Wj1I3h2FcS63kbHWaP/W307kDoOQPBZrvrQMi2zSc8+/ivYGmAMhzt2kYXECKt7+YDf9cn9f3D6KV0NSh5UqlpZvDfYXSc/6q2gHOMV3bPvMEf+uxxUEf06kzLH7HpnjExgmUcOM6uVNAudf7HgJ1h/JzUzHq/XLPoVAuyj2TbaHwvRCk+YqLJZw1D+8z3MkpZsdNF57quEZfDPahg371l1wzvZMH5sfFuSzUajD3lbIGRiV0o9PpmBp4qwnU63EkFy1wqhMjXjoviPqouRyZb+fDzxkDoyMuhAYjHcIuSlAs3/5htO2CzOCQL1t6cfEGaxG5U4ocHH0b0C5wvqNG5u9NDLbP7AjBS3a9TLPfmjijNBd4xwt/eYCpDkXirv/m/PVQLvbNzzf+zDWDApldxmv5rCxTLR,iv:FqShjMhe8Yzh2RiC991mVhATYZD2rdW3m0js9gQsKC4=,tag:8P7nEFVVY32IFP9C5H4cZA==,type:str] + users: ENC[AES256_GCM,data:w/2Vdq0EHXaJ5u/aA/reSCtwRHreWm1U1WoJT927xV81zoN0ytoYOwush610caZu8vVXkL4b0hysK77dyWJkdkYpwLY8xG9pLkYlU3lN5E/2tgEjB7Dd7oY7TFTCNuypmIzYh6V74KiHMeA0vlyWUp9lLNt40Ro3MZLT42DyTYjF6YBoUHUp0fS0rKypILJGobJBrwz2YWagXj80IqaaUmmsIcYAaM2u3dQviLlRkIyUxPd1wjFoMc/OMp5Y8A4ZHroCN0wJitGeEEP33GD+MUy58u05pA430AD5Mo4H2V7b3t0qIkOQ8a0BgSVA8UqmrcY/TfikuIZ1kTyCxvD7kmjPq5tG+bhtHt85wgk1XffVO3NDTK7UrltO8R6KolQ5bBgcKgl7YnFTN5qSAT+xrYg8oZaPrGQBTx6eEVETKHKe4oSDkGlAle86lenhF+jm3k2ALmH9X3P/TpAtfRhuU+sUKqhrqQ2Nf4M7LfBtd7lyt2ESqilKokcl51gWCY+1B75dCEIdb/BPmpwzJBGFOI2nZqhxFnVa8TyMpT7C2TxK7rCBPDt5NnNvWYc4+8sRXHBz7s2R5NTk4gaJODlo3HvyL0MV,iv:XlO48HKJWRgwsozmgXstfirwb5CUY+ywelbgLlcx/n4=,tag:GuQMkL2mpNkTJIep79x0zw==,type:str] forgejo: action_runner_token: ENC[AES256_GCM,data:yJ6OnRq5kinbuhvH06K5o3l86EafuBoojMwg/qhP+cgeH+BwPeE+Ng==,iv:IeXJahPxgLNIUFmkgp495tLVh8UyQBmJ2SnVEUhlhHs=,tag:XYQi613CxSp8AQeilJMrsg==,type:str] synapse: @@ -59,7 +59,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-02-24T07:24:41Z" - mac: ENC[AES256_GCM,data:wmTua89j8OYC4lw5nmDgKQy2A31KbI5M8jQxqNicHUEZFnDjo2aloNrpwKz5/lM5EomPvvJEAm2eyV04EmfYqKqmAXbcj2wTl4f6Afzb1X/+uABCvlaHquTXbx6lU8IyA31nHKeBspRX0mED86wrXasOsG34YJdDTa/lAKymjzk=,iv:Qea6St+3XfoVHAuWczK/rF2lEeKQBguRGpfGybdf7lA=,tag:yFufTgb7AG5dhrQH47Y0tA==,type:str] + lastmodified: "2026-02-25T07:35:41Z" + mac: ENC[AES256_GCM,data:UKAWLSj/OpyCGj1U9rhCX2rQr5E2CXodU+Z5RZddTdFis1+1opw7GLr+2s4OTRbREdZsNP3JSoXycgCssf4na88p/PTZh/VUa9ymbRr9eTacJq6ZkqRC5J8WyDK6gI+Qv4gv5CxdxZd92vUa4uXlwrZ4VsYepvrrkatCe9YTA9w=,iv:dkm+hkdyzJsIXp4uB36wYa/uzl8VA7LwhmvQT3hQlog=,tag:zHxeEze6RVfTCcduVkwuoQ==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From 20968322027e6e8846e699f6c591a3ef7de7c4a9 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Wed, 25 Feb 2026 08:39:14 +0100 Subject: [PATCH 064/108] Move Minecraft and Palworld modules to default.nix files Add sops secret for Palworld password --- .../{minecraft.nix => minecraft/default.nix} | 56 ++++++++++++------- modules/nixos/services/games/palworld.nix | 25 --------- .../nixos/services/games/palworld/default.nix | 30 ++++++++++ 3 files changed, 67 insertions(+), 44 deletions(-) rename modules/nixos/services/games/{minecraft.nix => minecraft/default.nix} (77%) delete mode 100644 modules/nixos/services/games/palworld.nix create mode 100644 modules/nixos/services/games/palworld/default.nix diff --git a/modules/nixos/services/games/minecraft.nix b/modules/nixos/services/games/minecraft/default.nix similarity index 77% rename from modules/nixos/services/games/minecraft.nix rename to modules/nixos/services/games/minecraft/default.nix index 7f408ae..84567b3 100644 --- a/modules/nixos/services/games/minecraft.nix +++ b/modules/nixos/services/games/minecraft/default.nix @@ -1,11 +1,16 @@ -{ inputs, config, lib, pkgs, namespace, ... }: -let +{ + inputs, + config, + lib, + pkgs, + namespace, + ... +}: let inherit (lib) mkIf mkEnableOption mkOption; inherit (lib.types) str; cfg = config.${namespace}.services.games.minecraft; -in -{ +in { imports = [ inputs.nix-minecraft.nixosModules.minecraft-servers ]; @@ -25,7 +30,7 @@ in }; config = mkIf cfg.enable { - user.users.${cfg.user} = { + users.users.${cfg.user} = { isSystemUser = true; group = cfg.group; }; @@ -77,7 +82,7 @@ in inherit whitelist; inherit jvmOpts; - package = pkgs.fabricServers.fabric-1_21_4.override { loaderVersion = "0.16.10"; }; + package = pkgs.fabricServers.fabric-1_21_4.override {loaderVersion = "0.16.10";}; serverProperties = { gamemode = "survival"; @@ -103,8 +108,14 @@ in inherit (pkgs) linkFarmFromDrvs fetchurl; in { mods = linkFarmFromDrvs "mods" (attrValues { - FabricApi = fetchurl { url = "https://cdn.modrinth.com/data/P7dR8mSH/versions/ZNwYCTsk/fabric-api-0.118.0%2B1.21.4.jar"; sha512 = "1e0d31b6663dc2c7be648f3a5a9cf7b698b9a0fd0f7ae16d1d3f32d943d7c5205ff63a4f81b0c4e94a8997482cce026b7ca486e99d9ce35ac069aeb29b02a30d"; }; - Terralith = fetchurl { url = "https://cdn.modrinth.com/data/8oi3bsk5/versions/MuJMtPGQ/Terralith_1.21.x_v2.5.8.jar"; sha512 = "f862ed5435ce4c11a97d2ea5c40eee9f817c908f3223b5fd3e3fff0562a55111d7429dc73a2f1ca0b1af7b1ff6fa0470ed6efebb5de13336c40bb70fb357dd60"; }; + FabricApi = fetchurl { + url = "https://cdn.modrinth.com/data/P7dR8mSH/versions/ZNwYCTsk/fabric-api-0.118.0%2B1.21.4.jar"; + sha512 = "1e0d31b6663dc2c7be648f3a5a9cf7b698b9a0fd0f7ae16d1d3f32d943d7c5205ff63a4f81b0c4e94a8997482cce026b7ca486e99d9ce35ac069aeb29b02a30d"; + }; + Terralith = fetchurl { + url = "https://cdn.modrinth.com/data/8oi3bsk5/versions/MuJMtPGQ/Terralith_1.21.x_v2.5.8.jar"; + sha512 = "f862ed5435ce4c11a97d2ea5c40eee9f817c908f3223b5fd3e3fff0562a55111d7429dc73a2f1ca0b1af7b1ff6fa0470ed6efebb5de13336c40bb70fb357dd60"; + }; # DistantHorizons = fetchurl { url = "https://cdn.modrinth.com/data/uCdwusMi/versions/jptcCdp2/DistantHorizons-2.2.1-a-1.20.4-forge-fabric.jar"; sha512 = "47368d91099d0b5f364339a69f4e425f8fb1e3a7c3250a8b649da76135e68a22f1a76b191c87e15a5cdc0a1d36bc57f2fa825490d96711d09d96807be97d575d"; }; }); }; @@ -125,7 +136,7 @@ in inherit whitelist; inherit jvmOpts; - package = pkgs.fabricServers.fabric-1_19_2.override { loaderVersion = "0.16.9"; }; + package = pkgs.fabricServers.fabric-1_19_2.override {loaderVersion = "0.16.9";}; serverProperties = { gamemode = "survival"; @@ -147,24 +158,31 @@ in inherit (lib) concatMapAttrs; readDirRec = src: dir: fn: - concatMapAttrs (name: type: if type == "directory" - then (readDirRec src "${dir}/${name}" fn) - else { "${dir}/${name}" = (fn "${dir}/${name}"); } + concatMapAttrs ( + name: type: + if type == "directory" + then (readDirRec src "${dir}/${name}" fn) + else {"${dir}/${name}" = fn "${dir}/${name}";} ) (readDir "${src}/${dir}"); copyDir = dir: readDirRec src dir (x: "${src}/${x}"); - in { - "ops.json" = { - value = ops; - }; - } - // (copyDir "config"); + in + { + "ops.json" = { + value = ops; + }; + } + // (copyDir "config"); symlinks = let inherit (builtins) attrNames readDir map; inherit (pkgs) linkFarm; - linkFarmFromDir = name: dir: linkFarm name (map (x: { name = x; path = "${src}/${dir}/${x}"; }) (attrNames (readDir "${src}/${dir}"))); + linkFarmFromDir = name: dir: + linkFarm name (map (x: { + name = x; + path = "${src}/${dir}/${x}"; + }) (attrNames (readDir "${src}/${dir}"))); in { Deftu = linkFarmFromDir "tekxit-deftu" "Deftu"; TKXAddons = linkFarmFromDir "tekxit-TKXAddons" "TKXAddons"; diff --git a/modules/nixos/services/games/palworld.nix b/modules/nixos/services/games/palworld.nix deleted file mode 100644 index dea16b3..0000000 --- a/modules/nixos/services/games/palworld.nix +++ /dev/null @@ -1,25 +0,0 @@ -{ config, lib, namespace, ... }: -let - inherit (lib) mkIf mkEnableOption; - - cfg = config.${namespace}.services.games.palworld; -in -{ - options.${namespace}.services.games.palworld = { - enable = mkEnableOption "Palworld"; - }; - - config = mkIf cfg.enable { -# kaas = (pkgs.mkSteamServer rec { -# name = "Palworld"; -# src = pkgs.fetchSteam { -# inherit name; -# appId = "2394010"; -# hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; -# }; -# -# sartCmd = "PalServer.sh"; -# hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; -# }); - }; -} diff --git a/modules/nixos/services/games/palworld/default.nix b/modules/nixos/services/games/palworld/default.nix new file mode 100644 index 0000000..152891d --- /dev/null +++ b/modules/nixos/services/games/palworld/default.nix @@ -0,0 +1,30 @@ +{ + config, + lib, + namespace, + ... +}: let + inherit (lib) mkIf mkEnableOption; + + cfg = config.${namespace}.services.games.palworld; +in { + options.${namespace}.services.games.palworld = { + enable = mkEnableOption "Palworld"; + }; + + config = mkIf cfg.enable { + # kaas = (pkgs.mkSteamServer rec { + # name = "Palworld"; + # src = pkgs.fetchSteam { + # inherit name; + # appId = "2394010"; + # hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; + # }; + # + # sartCmd = "PalServer.sh"; + # hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; + # }); + + sops.secrets."palworld/password" = {}; + }; +} From a2071e16a29d440c0d9a78eec56e7dcd15dd5a1f Mon Sep 17 00:00:00 2001 From: chris Date: Wed, 25 Feb 2026 07:59:25 +0000 Subject: [PATCH 065/108] chore(secrets): removed secret "some" from machine "aule" --- systems/x86_64-linux/aule/secrets.yml | 33 +++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 systems/x86_64-linux/aule/secrets.yml diff --git a/systems/x86_64-linux/aule/secrets.yml b/systems/x86_64-linux/aule/secrets.yml new file mode 100644 index 0000000..7069c7b --- /dev/null +++ b/systems/x86_64-linux/aule/secrets.yml @@ -0,0 +1,33 @@ +sops: + age: + - recipient: age19qfpf980tadguqq44zf6xwvjvl428dyrj46ha3n6aeqddwhtnuqqml7etq + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZMC9nRjVFWnZlMHJJK0Nl + dWFTR0FCUGNBYXIrUHlIUUphZll2QU9IOEZrCitFS3JvK3hYYmpEZ05aRStpdUd1 + L3JjNDl1Z2hQQ3FuNUZNM1hCRUtQUG8KLS0tIEg4VVEvVjZYN3JHSXljQW1xS3E4 + eVpyM1lSWExndlZhMkw2Vis4dVhjSVUKbk+z1h3Hb1A6SEbZ3g5vYui/FfkMyfxx + Zm67JenYittHvQggTIErAgJatTocfVB6Zy4FqJtPCOevTVrRTRkwAg== + -----END AGE ENCRYPTED FILE----- + - recipient: age1ewes0f5snqx3sh5ul6fa6qtxzhd25829v6mf5rx2wnheat6fefps5rme2x + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtOGJXWi9vUzdFbkx2NmVa + YnhITlNMc1RRRXoyOFNPN1B4VWQ5ZDUwNDFBCnVmdDFyUnptekxhOUlwdVcyRjFI + cHRSRkoyWnFVUDJMcXpVcmM5bjRKMkkKLS0tIDROWXR1UFFUa0NxcUtkdEwxQ2Vl + OW50OE9RMWpyT1AvS0QzZ3JVNDViYlkK77H0Uq3eRy0CHgH4bhdo7FVEJpKeR/DB + KZonll74qqsyW4n+hIbIybjaqtF3RBN4kj5ARuIGFmH8sAl6jSyHXA== + -----END AGE ENCRYPTED FILE----- + - recipient: age1jmrmdw4kmjeu9d6z74r2unqt7wpgsx24vqejmdjretsnsn8g4drsl3m98w + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZOURoRmk4QldEZExTRDYx + cXluYkg4OUFUNDNrQUNiNWRwKzhEQkdaemxzCnM3b25GYm5TM3NuNnBsVWRmQzNL + bTRabmx2UzBkN1dadlhwajN5RDIxVW8KLS0tIDhSQ1o4RGZBdlVHaHRKQWFyazU0 + N0lnMjMvREpmNWZvTUdiT0tjMk4vTk0KmIN1a3gjmFzaEwJBu41sw5Z61UgiO5fc + /pkS22BeVonuB12SmJX+77A1CxFz1EwM8HSShFKlpN2hPCJFJL7Nng== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-02-25T07:59:24Z" + mac: ENC[AES256_GCM,data:64AkqWb97nUciWtOOHP/SZhUeo/5ahxa0cN14ILw/jmToFkn8uDrSfY8/ibqBB0mmfhwGzcnI/5QpCLVzCSgG1J68bdPeSsYTZPwy2/0S0ven+GeqYHMfJ2Q1eJE7TONyOEvSdYdUWG+ff5t0qhSet9F2BgFnMSKcNeAaxIY6KU=,iv:aMQXbKk8oKSLBHIZyJLJahu5HHEMysmhcgfpDdZG+Ak=,tag:hqBVXis8MdqRorxttYeQaw==,type:str] + unencrypted_suffix: _unencrypted + version: 3.11.0 From d3a394dfd9d5960cccaaff6e8e9371be23840353 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Tue, 3 Mar 2026 14:59:58 +0100 Subject: [PATCH 066/108] Add LiveKit, coturn, and JWT service to Matrix module - Integrate LiveKit SFU, coturn TURN server, and lk-jwt-service for Element Call support in the Matrix Synapse module - Add firewall rules for new services and ports - Add key generation systemd service for LiveKit JWT - Extend Synapse config with TURN URIs and experimental features - Update Caddy config for new endpoints and well-known support - Improve OIDC config with additional scopes and user mapping - Add Grafana secret_key to SOPS secrets and config - Refactor and modularize secret checking in justfile scripts --- .just/machine.just | 1 + .just/vars.just | 69 +++++-- .../services/communication/matrix/default.nix | 183 +++++++++++++++++- .../observability/grafana/default.nix | 8 + 4 files changed, 234 insertions(+), 27 deletions(-) diff --git a/.just/machine.just b/.just/machine.just index 098d879..8d0d37f 100644 --- a/.just/machine.just +++ b/.just/machine.just @@ -7,5 +7,6 @@ [doc('Update the target machine')] [no-exit-message] @update machine: + cd .. && just vars _check {{ machine }} just assert '-d "../systems/x86_64-linux/{{ machine }}"' "Machine {{ machine }} does not exist, must be one of: $(ls ../systems/x86_64-linux/ | sed ':a;N;$!ba;s/\n/, /g')" nixos-rebuild switch -L --sudo --target-host {{ machine }} --build-host {{ machine }} --flake ..#{{ machine }} --log-format internal-json -v |& nom --json diff --git a/.just/vars.just b/.just/vars.just index 1ddfbfd..2c16d1b 100644 --- a/.just/vars.just +++ b/.just/vars.just @@ -23,7 +23,7 @@ edit machine: echo "Done" -[doc('Get var value by {key} of {machine}')] +[doc('Get var by {key} from {machine}')] get machine key: sops decrypt {{ base_path }}/{{ machine }}/secrets.yml | yq ".$(echo "{{ key }}" | sed -E 's/\//./g')" @@ -38,25 +38,52 @@ remove machine key: [script] check: + cd .. + for machine in $(ls {{ base_path }}); do - [ -f "{{ base_path }}/$machine/secrets.yml" ] || continue - [ -f "{{ base_path }}/$machine/default.nix" ] || continue - - echo "Processing $machine" - - mapfile -t missing < <(jq -nr \ - --rawfile defined <(nix eval --json --apply 'builtins.attrNames' ..#nixosConfigurations.$machine.config.sops.secrets 2>/dev/null) \ - --rawfile configured <(sops decrypt {{ base_path }}/$machine/secrets.yml | yq '.') \ - ' - $defined | fromjson as $def - | $configured - | fromjson - | paths(scalars) - | join("/") - | select(. | IN($def[]) | not) - ') - - if (( ${#missing[@]} > 0 )); then - printf 'missing the following %d secret(s):\n%s\n\n' "${#missing[@]}" "$(printf -- '- %s\n' "${missing[@]}")" - fi + just vars _check "$machine" done + +[no-exit-message] +[script] +_check machine: + # If the default nix file is missing, + # we can skip this folder as we are + # missing the files used to compare + # the defined vs the configured secrets + if [ ! -f "{{ base_path }}/{{ machine }}/default.nix" ]; then + printf "\rβ€’ %-8sskipped\n" "{{ machine }}" + exit 0 + fi + + exec 3< <(jq -nr \ + --rawfile defined <(nix eval --json ..#nixosConfigurations.{{ machine }}.config.sops.secrets 2>/dev/null) \ + --rawfile configured <([ -f "{{ base_path }}/{{ machine }}/secrets.yml" ] && sops decrypt {{ base_path }}/{{ machine }}/secrets.yml | yq '.' || echo "{}") \ + ' + [ $configured | fromjson | paths(scalars) | join("/") ] as $conf + | $defined + | fromjson + | map(.key | select(. | IN($conf[]) | not)) + | unique + | .[] + ') + + pid=$! # Process Id of the previous running command + spin='⠇⠋⠙⠸⒰⣠⣄⑆' + + i=0 + while kill -0 $pid 2>/dev/null + do + i=$(( (i+1) %${#spin} )) + printf "\r${spin:$i:1} %s" "{{ machine }}" + sleep .1 + done + + mapfile -t missing <&3 + + if (( ${#missing[@]} > 0 )); then + printf '\rβœ— %-8smissing %d secret(s):\n%s\n' "{{ machine }}" "${#missing[@]}" "$(printf -- ' %s\n' "${missing[@]}")" + exit 1 + else + printf "\rβœ“ %-8sup to date\n" "{{ machine }}" + fi diff --git a/modules/nixos/services/communication/matrix/default.nix b/modules/nixos/services/communication/matrix/default.nix index ccdbbaa..8bb79fe 100644 --- a/modules/nixos/services/communication/matrix/default.nix +++ b/modules/nixos/services/communication/matrix/default.nix @@ -15,6 +15,7 @@ port = 4001; database = "synapse"; + keyFile = "/var/lib/element-call/key"; in { options.${namespace}.services.communication.matrix = { enable = mkEnableOption "Matrix server (Synapse)"; @@ -26,8 +27,6 @@ in { # virtualisation.podman.enable = true; }; - networking.firewall.allowedTCPPorts = [4001]; - services = { matrix-synapse = { enable = true; @@ -55,8 +54,27 @@ in { password_config.enabled = true; backchannel_logout_enabled = true; + # Element Call options + max_event_delay_duration = "24h"; + rc_message = { + per_second = 0.5; + burst_count = 30; + }; + rc_delayed_event_mgmt = { + per_second = 1; + burst_count = 20; + }; + turn_uris = ["turn:turn.${domain}:4004?transport=udp" "turn:turn.${domain}:4004?transport=tcp"]; + experimental_features = { + # MSC2965: OAuth 2.0 Authorization Server Metadata discovery msc2965_enabled = true; + + # MSC3266: Room summary API. Used for knocking over federation + msc3266_enabled = true; + # MSC4222 needed for syncv2 state_after. This allow clients to + # correctly track the state of the room. + msc4222_enabled = true; }; sso = { @@ -181,33 +199,180 @@ in { caddy = { enable = true; + # globalConfig = '' + # layer4 { + # 127.0.0.1:4004 + # route { + # proxy { + # upstream synapse:4004 + # } + # } + # } + # 127.0.0.1:4005 + # route { + # proxy { + # upstream synapse:4005 + # } + # } + # } + # } + # ''; virtualHosts = let server = { "m.server" = "${fqn}:443"; }; client = { "m.homeserver".base_url = "https://${fqn}"; - "m.identity_server".base_url = "https://auth.kruining.eu"; + "m.identity_server".base_url = "https://auth.${domain}"; + "org.matrix.msc3575.proxy".url = "https://${domain}"; + "org.matrix.msc4143.rtc_foci" = [ + { + type = "livekit"; + livekit_service_url = "https://${domain}/livekit/jwt"; + } + ]; }; in { - "${domain}".extraConfig = '' + "${domain}, darkch.at".extraConfig = '' + # Route for lk-jwt-service + handle /livekit/jwt* { + uri strip_prefix /livekit/jwt + reverse_proxy http://[::1]:${toString config.services.lk-jwt-service.port} { + header_up Host {host} + header_up X-Forwarded-Server {host} + header_up X-Real-IP {remote_host} + header_up X-Forwarded-For {remote_host} + } + } + + handle_path /livekit/sfu* { + reverse_proxy http://[::1]:${toString config.services.livekit.settings.port} { + header_up Host {host} + header_up X-Forwarded-Server {host} + header_up X-Real-IP {remote_host} + header_up X-Forwarded-For {remote_host} + } + } + header /.well-known/matrix/* Content-Type application/json header /.well-known/matrix/* Access-Control-Allow-Origin * respond /.well-known/matrix/server `${toJSON server}` respond /.well-known/matrix/client `${toJSON client}` ''; "${fqn}".extraConfig = '' - reverse_proxy /_matrix/* http://::1:4001 - reverse_proxy /_synapse/client/* http://::1:4001 + reverse_proxy /_matrix/* http://::1:${toString port} + reverse_proxy /_synapse/client/* http://::1:${toString port} ''; }; }; + + livekit = { + enable = true; + openFirewall = true; + inherit keyFile; + + settings = { + port = 4002; + room.auto_create = false; + }; + }; + + lk-jwt-service = { + enable = true; + port = 4003; + # can be on the same virtualHost as synapse + livekitUrl = "wss://${domain}/livekit/sfu"; + inherit keyFile; + }; + + coturn = rec { + enable = true; + listening-port = 4004; + tls-listening-port = 40004; + no-cli = true; + no-tcp-relay = true; + min-port = 50000; + max-port = 50100; + use-auth-secret = true; + static-auth-secret-file = config.sops.secrets."coturn/secret".path; + realm = "turn.${domain}"; + # cert = "${config.security.acme.certs.${realm}.directory}/full.pem"; + # pkey = "${config.security.acme.certs.${realm}.directory}/key.pem"; + extraConfig = '' + # for debugging + verbose + # ban private IP ranges + no-multicast-peers + denied-peer-ip=0.0.0.0-0.255.255.255 + denied-peer-ip=10.0.0.0-10.255.255.255 + denied-peer-ip=100.64.0.0-100.127.255.255 + denied-peer-ip=127.0.0.0-127.255.255.255 + denied-peer-ip=169.254.0.0-169.254.255.255 + denied-peer-ip=172.16.0.0-172.31.255.255 + denied-peer-ip=192.0.0.0-192.0.0.255 + denied-peer-ip=192.0.2.0-192.0.2.255 + denied-peer-ip=192.88.99.0-192.88.99.255 + denied-peer-ip=192.168.0.0-192.168.255.255 + denied-peer-ip=198.18.0.0-198.19.255.255 + denied-peer-ip=198.51.100.0-198.51.100.255 + denied-peer-ip=203.0.113.0-203.0.113.255 + denied-peer-ip=240.0.0.0-255.255.255.255 + denied-peer-ip=::1 + denied-peer-ip=64:ff9b::-64:ff9b::ffff:ffff + denied-peer-ip=::ffff:0.0.0.0-::ffff:255.255.255.255 + denied-peer-ip=100::-100::ffff:ffff:ffff:ffff + denied-peer-ip=2001::-2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff + denied-peer-ip=2002::-2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff + denied-peer-ip=fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff + denied-peer-ip=fe80::-febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff + ''; + }; + }; + + networking.firewall = { + allowedTCPPortRanges = []; + allowedTCPPorts = [ + # Synapse + port + + # coTURN ports + config.services.coturn.listening-port + config.services.coturn.alt-listening-port + config.services.coturn.tls-listening-port + config.services.coturn.alt-tls-listening-port + ]; + allowedUDPPortRanges = with config.services.coturn; + lib.singleton { + from = min-port; + to = max-port; + }; + allowedUDPPorts = [ + # coTURN ports + config.services.coturn.listening-port + config.services.coturn.alt-listening-port + ]; + }; + + systemd = { + services.livekit-key = { + before = ["lk-jwt-service.service" "livekit.service"]; + wantedBy = ["multi-user.target"]; + path = with pkgs; [livekit coreutils gawk]; + script = '' + echo "Key missing, generating key" + echo "lk-jwt-service: $(livekit-server generate-keys | tail -1 | awk '{print $3}')" > "${keyFile}" + ''; + serviceConfig.Type = "oneshot"; + unitConfig.ConditionPathExists = "!${keyFile}"; + }; + services.lk-jwt-service.environment.LIVEKIT_FULL_ACCESS_HOMESERVERS = "${domain}"; }; sops = { secrets = { "synapse/oidc_id" = {}; "synapse/oidc_secret" = {}; + "coturn/secret" = {}; }; templates = { @@ -222,13 +387,19 @@ in { scopes: - openid - profile + - email + - offline_access client_id: '${config.sops.placeholder."synapse/oidc_id"}' client_secret: '${config.sops.placeholder."synapse/oidc_secret"}' backchannel_logout_enabled: true + user_profile_method: userinfo_endpoint + allow_existing_users: true + enable_registration: true user_mapping_provider: config: localpart_template: "{{ user.preferred_username }}" display_name_template: "{{ user.name }}" + email_template: "{{ user.email }}" ''; restartUnits = ["matrix-synapse.service"]; }; diff --git a/modules/nixos/services/observability/grafana/default.nix b/modules/nixos/services/observability/grafana/default.nix index 05d3570..e2040d4 100644 --- a/modules/nixos/services/observability/grafana/default.nix +++ b/modules/nixos/services/observability/grafana/default.nix @@ -30,6 +30,10 @@ in { domain = "ulmo"; }; + security = { + secret_key = "$__file{${config.sops.secrets."grafana/secret_key".path}}"; + }; + auth = { disable_login_form = false; oauth_auto_login = true; @@ -133,6 +137,10 @@ in { sops = { secrets = { + "grafana/secret_key" = { + owner = "grafana"; + group = "grafana"; + }; "grafana/oidc_id" = { owner = "grafana"; group = "grafana"; From 4e9ef9dc4f7df619478012bd5c813050417f8567 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Wed, 4 Mar 2026 09:29:36 +0100 Subject: [PATCH 067/108] Refactor Caddy config into networking.caddy module Move Caddy configuration from individual services to a shared networking.caddy module. Update service modules and system config to use the new interface. Remove redundant user definitions and old Caddy config blocks. --- .../authentication/authelia/default.nix | 84 ++++----- .../authentication/zitadel/default.nix | 37 ++-- .../services/communication/matrix/default.nix | 138 +++++++-------- .../services/development/forgejo/default.nix | 29 ++-- .../nixos/services/media/jellyfin/default.nix | 17 +- .../services/media/nextcloud/default.nix | 29 ++-- .../services/networking/caddy/default.nix | 40 +++++ .../services/security/vaultwarden/default.nix | 35 ++-- packages/studio/default.nix | 164 +++++++++--------- systems/x86_64-linux/ulmo/default.nix | 21 ++- 10 files changed, 308 insertions(+), 286 deletions(-) create mode 100644 modules/nixos/services/networking/caddy/default.nix diff --git a/modules/nixos/services/authentication/authelia/default.nix b/modules/nixos/services/authentication/authelia/default.nix index 9990003..7aea103 100644 --- a/modules/nixos/services/authentication/authelia/default.nix +++ b/modules/nixos/services/authentication/authelia/default.nix @@ -1,16 +1,36 @@ -{ config, lib, pkgs, namespace, ... }: -let +{ + config, + lib, + pkgs, + namespace, + ... +}: let inherit (lib) mkIf mkEnableOption; user = "authelia-testing"; cfg = config.${namespace}.services.authentication.authelia; -in -{ +in { options.${namespace}.services.authentication.authelia = { enable = mkEnableOption "Authelia"; }; config = mkIf cfg.enable { + ${namespace}.services.networking.caddy = { + hosts = { + "auth.kruining.eu".extraConfig = '' + reverse_proxy http://127.0.0.1:9091 + ''; + }; + extraConfig = '' + (auth) { + forward_auth http://127.0.0.1:9091 { + uri /api/authz/forward-auth + copy_headers Remote-User Remote-Groups Remote-Email Remote-Name + } + } + ''; + }; + environment.systemPackages = with pkgs; [ authelia ]; @@ -112,8 +132,8 @@ in authorization_policy = "one_factor"; userinfo_signed_response_alg = "none"; consent_mode = "implicit"; - scopes = [ "openid" "profile" "groups" ]; - redirect_uris = [ "https://jellyfin.kruining.eu/sso/OID/redirect/authelia" ]; + scopes = ["openid" "profile" "groups"]; + redirect_uris = ["https://jellyfin.kruining.eu/sso/OID/redirect/authelia"]; } { client_id = "streamarr"; @@ -127,8 +147,8 @@ in authorization_policy = "one_factor"; userinfo_signed_response_alg = "none"; consent_mode = "implicit"; - scopes = [ "offline_access" "openid" "email" "picture" "profile" "groups" ]; - redirect_uris = [ "http://localhost:3000/api/auth/oauth2/callback/authelia" ]; + scopes = ["offline_access" "openid" "email" "picture" "profile" "groups"]; + redirect_uris = ["http://localhost:3000/api/auth/oauth2/callback/authelia"]; } { client_id = "forgejo"; @@ -142,10 +162,10 @@ in authorization_policy = "one_factor"; userinfo_signed_response_alg = "none"; consent_mode = "implicit"; - scopes = [ "offline_access" "openid" "email" "picture" "profile" "groups" ]; - response_types = [ "code" ]; - grant_types = [ "authorization_code" ]; - redirect_uris = [ "http://localhost:5002/user/oauth2/authelia/callback" ]; + scopes = ["offline_access" "openid" "email" "picture" "profile" "groups"]; + response_types = ["code"]; + grant_types = ["authorization_code"]; + redirect_uris = ["http://localhost:5002/user/oauth2/authelia/callback"]; } ]; }; @@ -195,48 +215,8 @@ in - jellyfin-users - admin - dev - - jacqueline: - disabled: false - displayname: Jacqueline Bevers - password: $argon2id$v=19$m=65536,t=3,p=4$XgN8yEJV+syAE5yeos3HsA$SlN+j/lJfxJ5VxLu2CdrwowlCiWQNNGhIrSyDpohq18 - groups: - - jellyfin-users - - martijn: - disabled: false - displayname: Martijn Kruining - password: $argon2id$v=19$m=65536,t=3,p=4$XgN8yEJV+syAE5yeos3HsA$SlN+j/lJfxJ5VxLu2CdrwowlCiWQNNGhIrSyDpohq18 - groups: - - jellyfin-users - - andrea: - disabled: false - displayname: Andrea Kruining - password: $argon2id$v=19$m=65536,t=3,p=4$XgN8yEJV+syAE5yeos3HsA$SlN+j/lJfxJ5VxLu2CdrwowlCiWQNNGhIrSyDpohq18 - groups: - - jellyfin-users ''; }; }; - - services.caddy = { - enable = true; - virtualHosts = { - "auth.kruining.eu".extraConfig = '' - reverse_proxy http://127.0.0.1:9091 - ''; - }; - extraConfig = '' - (auth) { - forward_auth http://127.0.0.1:9091 { - uri /api/authz/forward-auth - copy_headers Remote-User Remote-Groups Remote-Email Remote-Name - } - } - ''; - }; - - networking.firewall.allowedTCPPorts = [ 80 443 ]; }; } diff --git a/modules/nixos/services/authentication/zitadel/default.nix b/modules/nixos/services/authentication/zitadel/default.nix index aaf64f6..082330e 100644 --- a/modules/nixos/services/authentication/zitadel/default.nix +++ b/modules/nixos/services/authentication/zitadel/default.nix @@ -537,7 +537,25 @@ in }; in mkIf cfg.enable { - ${namespace}.services.persistance.postgresql.enable = true; + ${namespace}.services = { + persistance.postgresql.enable = true; + + networking.caddy = { + hosts = { + "auth.kruining.eu" = '' + reverse_proxy h2c://::1:9092 + ''; + }; + extraConfig = '' + (auth) { + forward_auth h2c://::1:9092 { + uri /api/authz/forward-auth + copy_headers Remote-User Remote-Groups Remote-Email Remote-Name + } + } + ''; + }; + }; environment.systemPackages = with pkgs; [ zitadel @@ -678,23 +696,6 @@ in } ]; }; - - caddy = { - enable = true; - virtualHosts = { - "auth.kruining.eu".extraConfig = '' - reverse_proxy h2c://::1:9092 - ''; - }; - extraConfig = '' - (auth) { - forward_auth h2c://::1:9092 { - uri /api/authz/forward-auth - copy_headers Remote-User Remote-Groups Remote-Email Remote-Name - } - } - ''; - }; }; networking.firewall.allowedTCPPorts = [ 80 443 ]; diff --git a/modules/nixos/services/communication/matrix/default.nix b/modules/nixos/services/communication/matrix/default.nix index 8bb79fe..21fe777 100644 --- a/modules/nixos/services/communication/matrix/default.nix +++ b/modules/nixos/services/communication/matrix/default.nix @@ -25,6 +25,75 @@ in { ${namespace}.services = { persistance.postgresql.enable = true; # virtualisation.podman.enable = true; + + networking.caddy = { + # globalConfig = '' + # layer4 { + # 127.0.0.1:4004 + # route { + # proxy { + # upstream synapse:4004 + # } + # } + # } + # 127.0.0.1:4005 + # route { + # proxy { + # upstream synapse:4005 + # } + # } + # } + # } + # ''; + hosts = let + server = { + "m.server" = "${fqn}:443"; + }; + client = { + "m.homeserver".base_url = "https://${fqn}"; + "m.identity_server".base_url = "https://auth.${domain}"; + "org.matrix.msc3575.proxy".url = "https://${domain}"; + "org.matrix.msc4143.rtc_foci" = [ + { + type = "livekit"; + livekit_service_url = "https://${domain}/livekit/jwt"; + } + ]; + }; + in { + "${domain}, darkch.at" = '' + # Route for lk-jwt-service + handle /livekit/jwt* { + uri strip_prefix /livekit/jwt + reverse_proxy http://[::1]:${toString config.services.lk-jwt-service.port} { + header_up Host {host} + header_up X-Forwarded-Server {host} + header_up X-Real-IP {remote_host} + header_up X-Forwarded-For {remote_host} + } + } + + handle_path /livekit/sfu* { + reverse_proxy http://[::1]:${toString config.services.livekit.settings.port} { + header_up Host {host} + header_up X-Forwarded-Server {host} + header_up X-Real-IP {remote_host} + header_up X-Forwarded-For {remote_host} + } + } + + header /.well-known/matrix/* Content-Type application/json + header /.well-known/matrix/* Access-Control-Allow-Origin * + respond /.well-known/matrix/server `${toJSON server}` + respond /.well-known/matrix/client `${toJSON client}` + ''; + + "${fqn}" = '' + reverse_proxy /_matrix/* http://::1:${toString port} + reverse_proxy /_synapse/client/* http://::1:${toString port} + ''; + }; + }; }; services = { @@ -197,75 +266,6 @@ in { ]; }; - caddy = { - enable = true; - # globalConfig = '' - # layer4 { - # 127.0.0.1:4004 - # route { - # proxy { - # upstream synapse:4004 - # } - # } - # } - # 127.0.0.1:4005 - # route { - # proxy { - # upstream synapse:4005 - # } - # } - # } - # } - # ''; - virtualHosts = let - server = { - "m.server" = "${fqn}:443"; - }; - client = { - "m.homeserver".base_url = "https://${fqn}"; - "m.identity_server".base_url = "https://auth.${domain}"; - "org.matrix.msc3575.proxy".url = "https://${domain}"; - "org.matrix.msc4143.rtc_foci" = [ - { - type = "livekit"; - livekit_service_url = "https://${domain}/livekit/jwt"; - } - ]; - }; - in { - "${domain}, darkch.at".extraConfig = '' - # Route for lk-jwt-service - handle /livekit/jwt* { - uri strip_prefix /livekit/jwt - reverse_proxy http://[::1]:${toString config.services.lk-jwt-service.port} { - header_up Host {host} - header_up X-Forwarded-Server {host} - header_up X-Real-IP {remote_host} - header_up X-Forwarded-For {remote_host} - } - } - - handle_path /livekit/sfu* { - reverse_proxy http://[::1]:${toString config.services.livekit.settings.port} { - header_up Host {host} - header_up X-Forwarded-Server {host} - header_up X-Real-IP {remote_host} - header_up X-Forwarded-For {remote_host} - } - } - - header /.well-known/matrix/* Content-Type application/json - header /.well-known/matrix/* Access-Control-Allow-Origin * - respond /.well-known/matrix/server `${toJSON server}` - respond /.well-known/matrix/client `${toJSON client}` - ''; - "${fqn}".extraConfig = '' - reverse_proxy /_matrix/* http://::1:${toString port} - reverse_proxy /_synapse/client/* http://::1:${toString port} - ''; - }; - }; - livekit = { enable = true; openFirewall = true; diff --git a/modules/nixos/services/development/forgejo/default.nix b/modules/nixos/services/development/forgejo/default.nix index dfae9f0..f190b0c 100644 --- a/modules/nixos/services/development/forgejo/default.nix +++ b/modules/nixos/services/development/forgejo/default.nix @@ -28,6 +28,20 @@ in { ${namespace}.services = { persistance.postgresql.enable = true; virtualisation.podman.enable = true; + + networking.caddy = { + hosts = { + "${domain}" = '' + # import auth + + # stupid dumb way to prevent the login page and go to zitadel instead + # be aware that this does not disable local login at all! + # rewrite /user/login /user/oauth2/Zitadel + + reverse_proxy http://127.0.0.1:${toString cfg.port} + ''; + }; + }; }; environment.systemPackages = with pkgs; [forgejo]; @@ -168,21 +182,6 @@ in { }; }; }; - - caddy = { - enable = true; - virtualHosts = { - "${domain}".extraConfig = '' - # import auth - - # stupid dumb way to prevent the login page and go to zitadel instead - # be aware that this does not disable local login at all! - # rewrite /user/login /user/oauth2/Zitadel - - reverse_proxy http://127.0.0.1:${toString cfg.port} - ''; - }; - }; }; users = { diff --git a/modules/nixos/services/media/jellyfin/default.nix b/modules/nixos/services/media/jellyfin/default.nix index d4323f3..de19896 100644 --- a/modules/nixos/services/media/jellyfin/default.nix +++ b/modules/nixos/services/media/jellyfin/default.nix @@ -17,6 +17,14 @@ in { }; config = mkIf cfg.enable { + ${namespace}.services.networking.caddy = { + hosts = { + "jellyfin.kruining.eu" = '' + reverse_proxy http://[::1]:8096 + ''; + }; + }; + environment.systemPackages = with pkgs; [ jellyfin jellyfin-web @@ -34,15 +42,6 @@ in { user = "media"; group = "media"; }; - - caddy = { - enable = true; - virtualHosts = { - "jellyfin.kruining.eu".extraConfig = '' - reverse_proxy http://[::1]:8096 - ''; - }; - }; }; systemd.services.jellyfin.serviceConfig.killSignal = lib.mkForce "SIGKILL"; diff --git a/modules/nixos/services/media/nextcloud/default.nix b/modules/nixos/services/media/nextcloud/default.nix index 14d6863..06904c6 100644 --- a/modules/nixos/services/media/nextcloud/default.nix +++ b/modules/nixos/services/media/nextcloud/default.nix @@ -1,11 +1,15 @@ -{ config, lib, pkgs, namespace, ... }: -let +{ + config, + lib, + pkgs, + namespace, + ... +}: let inherit (lib) mkIf mkEnableOption mkOption; inherit (lib.types) str; cfg = config.${namespace}.services.media.nextcloud; -in -{ +in { options.${namespace}.services.media.nextcloud = { enable = mkEnableOption "Nextcloud"; @@ -21,6 +25,14 @@ in }; config = mkIf cfg.enable { + ${namespace}.services.networking.caddy = { + hosts."cloud.kruining.eu" = '' + php_fastcgi unix//run/phpfpm/nextcloud.sock { + env front_controller_active true + } + ''; + }; + users = { users.${cfg.user} = { isSystemUser = true; @@ -75,14 +87,5 @@ in # startServices = true; # }; - - services.caddy = { - enable = true; - virtualHosts."cloud.kruining.eu".extraConfig = '' - php_fastcgi unix//run/phpfpm/nextcloud.sock { - env front_controller_active true - } - ''; - }; }; } diff --git a/modules/nixos/services/networking/caddy/default.nix b/modules/nixos/services/networking/caddy/default.nix new file mode 100644 index 0000000..b70f7ae --- /dev/null +++ b/modules/nixos/services/networking/caddy/default.nix @@ -0,0 +1,40 @@ +{ + config, + pkgs, + lib, + namespace, + ... +}: let + inherit (builtins) length; + inherit (lib) mkIf mkEnableOption mkOption types attrNames mapAttrs; + + cfg = config.${namespace}.services.networking.caddy; + hasHosts = (cfg.hosts |> attrNames |> length) > 0; +in { + options.${namespace}.services.networking.caddy = { + enable = mkEnableOption "enable caddy" // {default = true;}; + + hosts = mkOption { + type = types.attrsOf types.str; + }; + + extraConfig = mkOption { + type = types.str; + }; + }; + + config = mkIf hasHosts { + services.caddy = { + enable = cfg.enable; + + package = pkgs.caddy.withPlugins { + plugins = ["https://github.com/corazawaf/coraza-caddy@2.1.0"]; + hash = lib.fakeHash; + }; + + virtualHosts = + cfg.hosts + |> mapAttrs (host: extraConfig: {inherit extraConfig;}); + }; + }; +} diff --git a/modules/nixos/services/security/vaultwarden/default.nix b/modules/nixos/services/security/vaultwarden/default.nix index 07f7058..7dce380 100644 --- a/modules/nixos/services/security/vaultwarden/default.nix +++ b/modules/nixos/services/security/vaultwarden/default.nix @@ -91,6 +91,22 @@ in { }; config = mkIf cfg.enable { + ${namespace}.services.networking.caddy.hosts = { + "vault.kruining.eu" = '' + encode zstd gzip + + handle_path /admin { + respond 401 { + close + } + } + + reverse_proxy http://localhost:${toString config.services.vaultwarden.config.ROCKET_PORT} { + header_up X-Real-IP {remote_host} + } + ''; + }; + systemd.tmpfiles.rules = [ "d '/var/lib/vaultwarden' 0700 vaultwarden vaultwarden - -" ]; @@ -150,25 +166,6 @@ in { } ]; }; - - caddy = { - enable = true; - virtualHosts = { - "vault.kruining.eu".extraConfig = '' - encode zstd gzip - - handle_path /admin { - respond 401 { - close - } - } - - reverse_proxy http://localhost:${toString config.services.vaultwarden.config.ROCKET_PORT} { - header_up X-Real-IP {remote_host} - } - ''; - }; - }; }; sops = { diff --git a/packages/studio/default.nix b/packages/studio/default.nix index 84610a3..1e6b457 100644 --- a/packages/studio/default.nix +++ b/packages/studio/default.nix @@ -1,105 +1,109 @@ -{ pkgs, inputs }: let +{ + pkgs, + inputs, +}: let inherit (builtins) fetchurl; - inherit (pkgs) makeDesktopItem copyDesktopItems wineWowPackages; + inherit (pkgs) makeDesktopItem copyDesktopItems wineWow64Packages; inherit (inputs.erosanix.lib.x86_64-linux) mkWindowsAppNoCC makeDesktopIcon copyDesktopIcons; - wine = wineWowPackages.base; -in mkWindowsAppNoCC rec { - inherit wine; + wine = wineWow64Packages.base; +in + mkWindowsAppNoCC rec { + inherit wine; - pname = "studio"; - version = "2.25.4"; + pname = "studio"; + version = "2.25.4"; - src = fetchurl { - url = "https://studio.download.bricklink.info/Studio2.0+EarlyAccess/Archive/2.25.12_1/Studio+2.0+EarlyAccess.exe"; - sha256 = "sha256:1xl3zvzkzr64zphk7rnpfx3whhbaykzw06m3nd5dc12r2p4sdh3v"; - }; + src = fetchurl { + url = "https://studio.download.bricklink.info/Studio2.0+EarlyAccess/Archive/2.25.12_1/Studio+2.0+EarlyAccess.exe"; + sha256 = "sha256:1xl3zvzkzr64zphk7rnpfx3whhbaykzw06m3nd5dc12r2p4sdh3v"; + }; - enableMonoBootPrompt = false; - dontUnpack = true; + enableMonoBootPrompt = false; + dontUnpack = true; - wineArch = "win64"; - enableInstallNotification = true; + wineArch = "win64"; + enableInstallNotification = true; - fileMap = { - "$HOME/.config/${pname}/Stud.io" = "drive_c/users/$USER/AppData/Local/Stud.io"; - "$HOME/.config/${pname}/Bricklink" = "drive_c/users/$USER/AppData/LocalLow/Bricklink"; - }; + fileMap = { + "$HOME/.config/${pname}/Stud.io" = "drive_c/users/$USER/AppData/Local/Stud.io"; + "$HOME/.config/${pname}/Bricklink" = "drive_c/users/$USER/AppData/LocalLow/Bricklink"; + }; - fileMapDuringAppInstall = false; + fileMapDuringAppInstall = false; - persistRegistry = false; - persistRuntimeLayer = true; - inputHashMethod = "version"; + persistRegistry = false; + persistRuntimeLayer = true; + inputHashMethod = "version"; - # Can be used to precisely select the Direct3D implementation. - # - # | enableVulkan | rendererOverride | Direct3D implementation | - # |--------------|------------------|-------------------------| - # | false | null | OpenGL | - # | true | null | Vulkan (DXVK) | - # | * | dxvk-vulkan | Vulkan (DXVK) | - # | * | wine-opengl | OpenGL | - # | * | wine-vulkan | Vulkan (VKD3D) | - enableVulkan = false; - rendererOverride = null; + # Can be used to precisely select the Direct3D implementation. + # + # | enableVulkan | rendererOverride | Direct3D implementation | + # |--------------|------------------|-------------------------| + # | false | null | OpenGL | + # | true | null | Vulkan (DXVK) | + # | * | dxvk-vulkan | Vulkan (DXVK) | + # | * | wine-opengl | OpenGL | + # | * | wine-vulkan | Vulkan (VKD3D) | + enableVulkan = false; + rendererOverride = null; - enableHUD = false; + enableHUD = false; - enabledWineSymlinks = { }; - graphicsDriver = "auto"; - inhibitIdle = false; + enabledWineSymlinks = {}; + graphicsDriver = "auto"; + inhibitIdle = false; - nativeBuildInputs = [ copyDesktopIcons copyDesktopItems ]; + nativeBuildInputs = [copyDesktopIcons copyDesktopItems]; - winAppInstall = '' - wine64 ${src} + winAppInstall = '' + wine64 ${src} - wineserver -W - wine64 reg add 'HKEY_CURRENT_USER\Software\Wine\X11 Driver' /t REG_SZ /v UseTakeFocus /d N /f - ''; + wineserver -W + wine64 reg add 'HKEY_CURRENT_USER\Software\Wine\X11 Driver' /t REG_SZ /v UseTakeFocus /d N /f + ''; - winAppPreRun = '' - wineserver -W - wine64 reg add 'HKEY_CURRENT_USER\Software\Wine\X11 Driver' /t REG_SZ /v UseTakeFocus /d N /f - ''; + winAppPreRun = '' + wineserver -W + wine64 reg add 'HKEY_CURRENT_USER\Software\Wine\X11 Driver' /t REG_SZ /v UseTakeFocus /d N /f + ''; - winAppRun = '' - wine64 "$WINEPREFIX/drive_c/Program Files/Studio 2.0/Studio.exe" "$ARGS" - ''; + winAppRun = '' + wine64 "$WINEPREFIX/drive_c/Program Files/Studio 2.0/Studio.exe" "$ARGS" + ''; - winAppPostRun = ""; - installPhase = '' - runHook preInstall + winAppPostRun = ""; + installPhase = '' + runHook preInstall - ln -s $out/bin/.launcher $out/bin/${pname} + ln -s $out/bin/.launcher $out/bin/${pname} - runHook postInstall - ''; + runHook postInstall + ''; - desktopItems = [ - (makeDesktopItem { - mimeTypes = []; + desktopItems = [ + (makeDesktopItem { + mimeTypes = []; + name = pname; + exec = pname; + icon = pname; + desktopName = "Bricklink studio"; + genericName = "Lego creation app"; + categories = []; + }) + ]; + + desktopIcon = makeDesktopIcon { name = pname; - exec = pname; - icon = pname; - desktopName = "Bricklink studio"; - genericName = "Lego creation app"; - categories = []; - }) - ]; + src = ./studio.png; + }; - desktopIcon = makeDesktopIcon { - name = pname; - src = ./studio.png; - }; - - meta = { - description = "App for creating lego builds"; - homepage = "https://www.bricklink.com/v3/studio/main.page"; - license = ""; - maintainers = []; - platforms = [ "x86_64-linux" ]; - }; -} + meta = { + description = "App for creating lego builds"; + homepage = "https://www.bricklink.com/v3/studio/main.page"; + license = ""; + maintainers = []; + platforms = ["x86_64-linux"]; + }; + } diff --git a/systems/x86_64-linux/ulmo/default.nix b/systems/x86_64-linux/ulmo/default.nix index 7440933..43a5760 100644 --- a/systems/x86_64-linux/ulmo/default.nix +++ b/systems/x86_64-linux/ulmo/default.nix @@ -27,17 +27,6 @@ }; }; - # Expose amarht cloud stuff like this until I have a proper solution - services.caddy.virtualHosts = { - "auth.amarth.cloud".extraConfig = '' - reverse_proxy http://192.168.1.223:9092 - ''; - - "amarth.cloud".extraConfig = '' - reverse_proxy http://192.168.1.223:8080 - ''; - }; - # virtualisation = { # containers.enable = true; # podman = { @@ -204,6 +193,16 @@ development.forgejo.enable = true; networking.ssh.enable = true; + networking.caddy.hosts = { + # Expose amarht cloud stuff like this until I have a proper solution + "auth.amarth.cloud" = '' + reverse_proxy http://192.168.1.223:9092 + ''; + + "amarth.cloud" = '' + reverse_proxy http://192.168.1.223:8080 + ''; + }; media.enable = true; media.glance.enable = true; From e69a7a86698a05fb2045a3f5d3af78f383d946f1 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Wed, 4 Mar 2026 09:55:43 +0100 Subject: [PATCH 068/108] Update machine docstring --- .just/machine.just | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.just/machine.just b/.just/machine.just index 8d0d37f..cf1c8f7 100644 --- a/.just/machine.just +++ b/.just/machine.just @@ -4,7 +4,7 @@ @list: ls -1 ../systems/x86_64-linux/ -[doc('Update the target machine')] +[doc('Update target machine')] [no-exit-message] @update machine: cd .. && just vars _check {{ machine }} From a97c244c4f94c4643a2aad4914c1f7490fb64ffa Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Wed, 4 Mar 2026 10:43:57 +0100 Subject: [PATCH 069/108] Update Caddy plugin source and clear hash in config --- modules/nixos/services/networking/caddy/default.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/nixos/services/networking/caddy/default.nix b/modules/nixos/services/networking/caddy/default.nix index b70f7ae..2274efa 100644 --- a/modules/nixos/services/networking/caddy/default.nix +++ b/modules/nixos/services/networking/caddy/default.nix @@ -28,8 +28,8 @@ in { enable = cfg.enable; package = pkgs.caddy.withPlugins { - plugins = ["https://github.com/corazawaf/coraza-caddy@2.1.0"]; - hash = lib.fakeHash; + plugins = ["github.com/corazawaf/coraza-caddy/v2@2.1.0"]; + hash = ""; }; virtualHosts = From 95ae5b8b836132315654a36ec41f95fe7188014f Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Wed, 4 Mar 2026 10:44:11 +0100 Subject: [PATCH 070/108] Refactor sabnzbd config to use settings and secretFiles Switch sabnzbd configuration to use the settings and secretFiles options instead of a static config file. Add support for nzbkey secret. Update sops template to include nzb_key and remove duplicated server and misc settings. --- .../nixos/services/media/servarr/default.nix | 49 +++++++++---------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/modules/nixos/services/media/servarr/default.nix b/modules/nixos/services/media/servarr/default.nix index bc911f7..e64c6fe 100644 --- a/modules/nixos/services/media/servarr/default.nix +++ b/modules/nixos/services/media/servarr/default.nix @@ -98,8 +98,28 @@ in { sabnzbd = { enable = true; openFirewall = true; - configFile = "/var/media/sabnzbd/config.ini"; - # configFile = config.sops.templates."sabnzbd/config.ini".path; + + secretFiles = [ + config.sops.templates."sabnzbd/config.ini".path + ]; + + settings = { + misc = { + port = 2009; + + download_dir = "/var/media/downloads/incomplete"; + complete_dir = "/var/media/downloads/done"; + }; + + servers = { + "news.sunnyusenet.com" = { + displayname = "news.sunnyusenet.com"; + host = "news.sunnyusenet.com"; + port = 563; + timeout = 60; + }; + }; + }; user = "sabnzbd"; group = "media"; @@ -402,6 +422,7 @@ in { secrets = { "qbittorrent/password" = {}; "sabnzbd/apikey" = {}; + "sabnzbd/nzbkey" = {}; "sabnzbd/sunnyweb/username" = {}; "sabnzbd/sunnyweb/password" = {}; }; @@ -428,36 +449,14 @@ in { group = "media"; mode = "0660"; content = '' - __version__ = 19 - __encoding__ = utf-8 [misc] - download_dir = /var/media/downloads/incomplete - complete_dir = /var/media/downloads/done api_key = ${config.sops.placeholder."sabnzbd/apikey"} - log_dir = logs + nzb_key = ${config.sops.placeholder."sabnzbd/nzbkey"} [servers] [[news.sunnyusenet.com]] - name = news.sunnyusenet.com - displayname = news.sunnyusenet.com - host = news.sunnyusenet.com - port = 563 - timeout = 60 username = ${config.sops.placeholder."sabnzbd/sunnyweb/username"} password = ${config.sops.placeholder."sabnzbd/sunnyweb/password"} - connections = 8 - ssl = 1 - ssl_verify = 3 - ssl_ciphers = "" - enable = 1 - required = 0 - optional = 0 - retention = 0 - expire_date = "" - quota = "" - usage_at_start = 0 - priority = 1 - notes = "" ''; }; }; From 3ae310f2e19da24ad27a0fc0194bb424dd3c0674 Mon Sep 17 00:00:00 2001 From: chris Date: Wed, 4 Mar 2026 09:45:12 +0000 Subject: [PATCH 071/108] chore(secrets): set secret "sabnzbd/nzbkey" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 729bed1..026b948 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -35,6 +35,7 @@ sabnzbd: password: ENC[AES256_GCM,data:flw8AahqO1Mx,iv:Qhu8iVWMzzqy18y8dj3aHoBnSZatm74/tYvZ456l2sA=,tag:sCYBdw7kD0zJZFFr5EyPIQ==,type:str] username: ENC[AES256_GCM,data:IboJ8WDWuVNgvrk7c3V8I5S6Xg==,iv:BRohMuQFQz2S+HFasIaok6npT3C5v/SlhAhbLQXfB0s=,tag:M3/u0WBQ3AufHqe4DCtsrA==,type:str] apikey: ENC[AES256_GCM,data:j5sPXKbBhMdNHOuoTfZ+c8nGu5JameOgK2z428iLdP01Hi6MvHVaN8Zs8YxMoSBtOjdtIEC8MS+3m1S1rU/P4pCRfZpK5ua1DBHq4l0xROUqokFWjDcAmJJv3pYXl0cQxQcGKQ==,iv:v5hu3gmO1Zn1FfXkHLPGN9f7JOcQjzoQahdqJwfM+xY=,tag:uI1LFcTgcyRgAaTJ1kzKow==,type:str] + nzbkey: ENC[AES256_GCM,data:tGFnZ24XNI7U8pVYq45ENSVTeVkkcWfT5/NewqSJ3sm7Bexxml/PFTMBIl+97mWzNMMFklBurX/115P06NHCj1mxEvIjIc1bF4yuYhZFdSTlqRVWaESE/Ei7gke758FCt37N43wADgaKj4i5jizDHJMIbaw8ncP3qBSCy1F4BAU=,iv:RA+3oYGhVLBG+ikHMwBG3t2iN15lGsncdmlkfF6vJhY=,tag:6FNM18KCSzzpIXYDpQfHSg==,type:str] whisparr: apikey: ENC[AES256_GCM,data:kIGCsd4mszm90PoQMzlSEBKw9Ow0GvP1qdLtwXYKkAb6b65l89v8lMWJ2X1MyD2gJX+P+Bv1F/2BSjUFXErq/UYnp4dAjwKi/ezGCbhjMutDM1FvwFWEHRnR3gjd9uXPWJ8Xhg==,iv:98aPQlcZHJovpnzACDs6RtKblLnHg6wyi+Er5DAowj8=,tag:Tl8jz/pWYWAtBCfoztKdyw==,type:str] coturn: @@ -59,7 +60,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-02-25T07:35:41Z" - mac: ENC[AES256_GCM,data:UKAWLSj/OpyCGj1U9rhCX2rQr5E2CXodU+Z5RZddTdFis1+1opw7GLr+2s4OTRbREdZsNP3JSoXycgCssf4na88p/PTZh/VUa9ymbRr9eTacJq6ZkqRC5J8WyDK6gI+Qv4gv5CxdxZd92vUa4uXlwrZ4VsYepvrrkatCe9YTA9w=,iv:dkm+hkdyzJsIXp4uB36wYa/uzl8VA7LwhmvQT3hQlog=,tag:zHxeEze6RVfTCcduVkwuoQ==,type:str] + lastmodified: "2026-03-04T09:45:11Z" + mac: ENC[AES256_GCM,data:MHxByUlNdiK5/knEtWjHcCN6dpZ8x7h62d2TCCSOnFYGs2RbOmKbh6jsslVg2rVpapF/P8ZysAg2lRCSjils1csoFI3fclgXJtxG0g24v2OtpN2Ny0tLixNVGi++PxFayZUsjfBMh/cHIJgstieSFTR3vl7/SMvo5HsxHBvPhmE=,iv:v9HD9QrQSABnYJd/YtGIOwBP5tT3FL9QUoogl7m/7bI=,tag:+L0rljfs6LDkX6/SYzDt4Q==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From 9b2bc8e6047c68cb7ddbdb133abf927ec07faebc Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 5 Mar 2026 08:24:46 +0000 Subject: [PATCH 072/108] chore(secrets): set secret "qbittorrent/password" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 026b948..fe42e2d 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -25,7 +25,7 @@ mydia: guardian_secret: ENC[AES256_GCM,data:OjnNFSHlecL+qXwlhTm++itRM6ga5E5KrSJxbgIUpbMEkIWgu3xhRtnPdipXbedgall0XdO/s+jnWCagZX94BA==,iv:DukdKvm9vey8BWUiml20tgA/Vji1XVX4+sUPge9nTk0=,tag:q3HdvgUYqR0APiaFz0ul5Q==,type:str] qbittorrent: password_hash: ENC[AES256_GCM,data:yCfCslj01wtfwzzPOGlwA6wLLf+EUuEweYa3ZxvDtd/VGMxuV38quV+ob1Of+W0UH3+U4Qmgh4BK3I3IJZuKOvNdkZ0i81YBwW6cgvZUmnxwh8wokpNzxCKbYk5nF7y7SaGEdzQLvV7ad3fNMJsQ+s2zCsKWbm+j8Bwgq0E=,iv:IIktPS9pYXaYPzH0r4wrkp31CpunKnr70Ainu6hOeWY=,tag:bYCfhDfIwiQZ1tKAvITewQ==,type:str] - password: ENC[AES256_GCM,data:UepYY6UjJV/jo2aXTOEnKRtsjSqOSYPQlKlrAa7rf9rdnt2UXGjCkvN+A72pICuIBCAmhXZBAUMvmWTV9trk6NREHe0cY1xTC7pNv3x9TM/ZQmH498pbT/95pYAKwouHp9heJQ==,iv:FzjF+xPoaOp+gplxpz940V2dkWSTWe8dWUxexCoxxHc=,tag:TDZsboq9fEmmBrwJN/HTpQ==,type:str] + password: ENC[AES256_GCM,data:0UlgUE3IGShKbP6XdLQ6JCIjvyK7RwzRMP8mN4f2dUUQHXV1b0C2NGk4cy9YLKR7kBicbX0OojKV2k6766tjBHFeHUOfIPEoTv1VpRyoVwe6PQKThHLe4xK+kbWKq+9QXA1IaPr+KDw5odIusBCNPJbrGUOGjK2ROR7jWvWUdLw=,iv:LaQGvFXXNiS20nOj6n9OpUJ5miaDJ5t3zArhYCluGxY=,tag:kIYOVh8H4iGUyiDX8CT2FQ==,type:str] grafana: oidc_id: ENC[AES256_GCM,data:NVdIgCQ6nz4BSUDJYCKyILtK,iv:tcljy9PzC/yyd7TSdngyJt+uh60uXi2PKu47czErbaQ=,tag:zE4q3dD4UQaHIpGeZ1L48Q==,type:str] oidc_secret: ENC[AES256_GCM,data:b7qILK9ZHW2khtM1Hl/KdjCv3Wq6eOo2Ym/cbjcMB8/3Hn2UelpP4K4lFyiV3bn1/GF6Jl5Z7A0EwMybOx0InA==,iv:3HL/7BiyObwT8DmFxzNPI9CdmCH/4j/4oc9x7qBE1k0=,tag:dBhcq1zLKy6N+jp/v42R4A==,type:str] @@ -60,7 +60,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-03-04T09:45:11Z" - mac: ENC[AES256_GCM,data:MHxByUlNdiK5/knEtWjHcCN6dpZ8x7h62d2TCCSOnFYGs2RbOmKbh6jsslVg2rVpapF/P8ZysAg2lRCSjils1csoFI3fclgXJtxG0g24v2OtpN2Ny0tLixNVGi++PxFayZUsjfBMh/cHIJgstieSFTR3vl7/SMvo5HsxHBvPhmE=,iv:v9HD9QrQSABnYJd/YtGIOwBP5tT3FL9QUoogl7m/7bI=,tag:+L0rljfs6LDkX6/SYzDt4Q==,type:str] + lastmodified: "2026-03-05T08:24:45Z" + mac: ENC[AES256_GCM,data:fSys6LGZsgYyaF0MOL3pQzJwD4D+R/26Lk1rbgVwza9xuBY1xWXM8h1Uz9nbLjyOQ9gbFGiNCpX0+g1lxRY5jNdCYChVt0ubFNMFBGGRH4ASRDkPe+Y+7b/l9V7tMV5A4rOrJ0A3ur9FMnYptpdniCjb7Uyvp5yJr6fZYf4kAJA=,iv:no77DeCBgY+jhfUjAfU9PuOYZdmIF7bwnVJ/9nlRRAs=,tag:fLe6cEnw7Fazquk2VrjDig==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From fb8ead924a8214e273e980ae6d4b250341011e2c Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 5 Mar 2026 08:25:19 +0000 Subject: [PATCH 073/108] chore(secrets): set secret "qbittorrent/password_hash" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index fe42e2d..a30b644 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -24,7 +24,7 @@ mydia: secret_key_base: ENC[AES256_GCM,data:yG7HJ5r74Qtxbeyf8F6dA0uHv2pQ8YAJKlKiKjS+m24JRvJWQaTThJ+c5HbuUa6R3e9XtVHchhlVPkF0Is/b+g==,iv:v65xdRr4JdKZmBtjZ08/J3LLqnphSGt9QfVPNQ2x/xg=,tag:n7tD2dhr4IJn1LWM9WW8UA==,type:str] guardian_secret: ENC[AES256_GCM,data:OjnNFSHlecL+qXwlhTm++itRM6ga5E5KrSJxbgIUpbMEkIWgu3xhRtnPdipXbedgall0XdO/s+jnWCagZX94BA==,iv:DukdKvm9vey8BWUiml20tgA/Vji1XVX4+sUPge9nTk0=,tag:q3HdvgUYqR0APiaFz0ul5Q==,type:str] qbittorrent: - password_hash: ENC[AES256_GCM,data:yCfCslj01wtfwzzPOGlwA6wLLf+EUuEweYa3ZxvDtd/VGMxuV38quV+ob1Of+W0UH3+U4Qmgh4BK3I3IJZuKOvNdkZ0i81YBwW6cgvZUmnxwh8wokpNzxCKbYk5nF7y7SaGEdzQLvV7ad3fNMJsQ+s2zCsKWbm+j8Bwgq0E=,iv:IIktPS9pYXaYPzH0r4wrkp31CpunKnr70Ainu6hOeWY=,tag:bYCfhDfIwiQZ1tKAvITewQ==,type:str] + password_hash: ENC[AES256_GCM,data:A0X1YubbL7rbZV2vPCOY0cv3Imy3r+EY9CNWzJiiLril/EXlZlUC+vB2O/c2oK/lhrQc/OUG0Zejwzkitujnk9qkIpfUZh52D5ehSK5DYiedevpCtCFC4Uu16vxyZJVxCbU5Hj9Lbu+2MOdPFD7Fgk4K1E9TpNDuncaKCNI=,iv:dxA0t80pW+5KeNGtDCCEy9AlsCD+HqiTYo2smYJnkqw=,tag:9o1+rgwX9mfyuQ7YTAMLPQ==,type:str] password: ENC[AES256_GCM,data:0UlgUE3IGShKbP6XdLQ6JCIjvyK7RwzRMP8mN4f2dUUQHXV1b0C2NGk4cy9YLKR7kBicbX0OojKV2k6766tjBHFeHUOfIPEoTv1VpRyoVwe6PQKThHLe4xK+kbWKq+9QXA1IaPr+KDw5odIusBCNPJbrGUOGjK2ROR7jWvWUdLw=,iv:LaQGvFXXNiS20nOj6n9OpUJ5miaDJ5t3zArhYCluGxY=,tag:kIYOVh8H4iGUyiDX8CT2FQ==,type:str] grafana: oidc_id: ENC[AES256_GCM,data:NVdIgCQ6nz4BSUDJYCKyILtK,iv:tcljy9PzC/yyd7TSdngyJt+uh60uXi2PKu47czErbaQ=,tag:zE4q3dD4UQaHIpGeZ1L48Q==,type:str] @@ -60,7 +60,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-03-05T08:24:45Z" - mac: ENC[AES256_GCM,data:fSys6LGZsgYyaF0MOL3pQzJwD4D+R/26Lk1rbgVwza9xuBY1xWXM8h1Uz9nbLjyOQ9gbFGiNCpX0+g1lxRY5jNdCYChVt0ubFNMFBGGRH4ASRDkPe+Y+7b/l9V7tMV5A4rOrJ0A3ur9FMnYptpdniCjb7Uyvp5yJr6fZYf4kAJA=,iv:no77DeCBgY+jhfUjAfU9PuOYZdmIF7bwnVJ/9nlRRAs=,tag:fLe6cEnw7Fazquk2VrjDig==,type:str] + lastmodified: "2026-03-05T08:25:18Z" + mac: ENC[AES256_GCM,data:pg9M9RtPFezugxTgIQnmxBl2pjNJIJNOuvO+xTJ2jZIiZy4nRTdpnQmNzJtKKS3RN2wA+Ji18qilGL3L5Gy9K8YXa8dCnHZRd+BftBFxlTDUkZGUR34sDA8ljQmZ2ii+nqEocuLaYiY3qyiYyY5Fe2lJATpmQFNoWErQ2f1tNKk=,iv:YpPubbl+7PCLCotifbiAiSpVFyhz4poheY/Zc+bnLQI=,tag:qHMl9q/71XxEUDhtZpVV1Q==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From 939cab4383fcd7ce1ccd82d77469a79a528b2aa5 Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 5 Mar 2026 10:32:25 +0000 Subject: [PATCH 074/108] chore(secrets): set secret "qbittorrent/password_hash" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index a30b644..5b235d4 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -24,7 +24,7 @@ mydia: secret_key_base: ENC[AES256_GCM,data:yG7HJ5r74Qtxbeyf8F6dA0uHv2pQ8YAJKlKiKjS+m24JRvJWQaTThJ+c5HbuUa6R3e9XtVHchhlVPkF0Is/b+g==,iv:v65xdRr4JdKZmBtjZ08/J3LLqnphSGt9QfVPNQ2x/xg=,tag:n7tD2dhr4IJn1LWM9WW8UA==,type:str] guardian_secret: ENC[AES256_GCM,data:OjnNFSHlecL+qXwlhTm++itRM6ga5E5KrSJxbgIUpbMEkIWgu3xhRtnPdipXbedgall0XdO/s+jnWCagZX94BA==,iv:DukdKvm9vey8BWUiml20tgA/Vji1XVX4+sUPge9nTk0=,tag:q3HdvgUYqR0APiaFz0ul5Q==,type:str] qbittorrent: - password_hash: ENC[AES256_GCM,data:A0X1YubbL7rbZV2vPCOY0cv3Imy3r+EY9CNWzJiiLril/EXlZlUC+vB2O/c2oK/lhrQc/OUG0Zejwzkitujnk9qkIpfUZh52D5ehSK5DYiedevpCtCFC4Uu16vxyZJVxCbU5Hj9Lbu+2MOdPFD7Fgk4K1E9TpNDuncaKCNI=,iv:dxA0t80pW+5KeNGtDCCEy9AlsCD+HqiTYo2smYJnkqw=,tag:9o1+rgwX9mfyuQ7YTAMLPQ==,type:str] + password_hash: ENC[AES256_GCM,data:XPax8MLYtyRRBUIcH2Q36VSGh+0mtdt1GBAWnKsacOS0q1vIVaqek64w7Dcav/y1vNtIC6NeE1odCO1UvY8Ed+Y/NDVWSc3L1Sh0ZhcwgF9kqywQ5bBDmIHWF7Mcx+f7MHKjbWtycJGBObRYZnqEs4qeO8gaJUSfz0J2iUM=,iv:86kwmRSOusePAUISxrinoGaSJfAr3cSryM6A3gQBKG4=,tag:WNmVWBTv+vT1hfsKvhzoOg==,type:str] password: ENC[AES256_GCM,data:0UlgUE3IGShKbP6XdLQ6JCIjvyK7RwzRMP8mN4f2dUUQHXV1b0C2NGk4cy9YLKR7kBicbX0OojKV2k6766tjBHFeHUOfIPEoTv1VpRyoVwe6PQKThHLe4xK+kbWKq+9QXA1IaPr+KDw5odIusBCNPJbrGUOGjK2ROR7jWvWUdLw=,iv:LaQGvFXXNiS20nOj6n9OpUJ5miaDJ5t3zArhYCluGxY=,tag:kIYOVh8H4iGUyiDX8CT2FQ==,type:str] grafana: oidc_id: ENC[AES256_GCM,data:NVdIgCQ6nz4BSUDJYCKyILtK,iv:tcljy9PzC/yyd7TSdngyJt+uh60uXi2PKu47czErbaQ=,tag:zE4q3dD4UQaHIpGeZ1L48Q==,type:str] @@ -60,7 +60,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-03-05T08:25:18Z" - mac: ENC[AES256_GCM,data:pg9M9RtPFezugxTgIQnmxBl2pjNJIJNOuvO+xTJ2jZIiZy4nRTdpnQmNzJtKKS3RN2wA+Ji18qilGL3L5Gy9K8YXa8dCnHZRd+BftBFxlTDUkZGUR34sDA8ljQmZ2ii+nqEocuLaYiY3qyiYyY5Fe2lJATpmQFNoWErQ2f1tNKk=,iv:YpPubbl+7PCLCotifbiAiSpVFyhz4poheY/Zc+bnLQI=,tag:qHMl9q/71XxEUDhtZpVV1Q==,type:str] + lastmodified: "2026-03-05T10:32:25Z" + mac: ENC[AES256_GCM,data:Z73v4OqURDMyr8ZXs9SeZsjjY8BpH5T/E4fOJE0CAb13l0SD0MyPv7H7vZdhtiOftMk+LxxH48MaAjyC+7RQwJExA17hA+ZDcDT3JtnxX+35aBaZVPspKKHip5LXN1J2z32gcgW1+9qXhahP0H9RtkxckDSEL7zzyvM4Zji14j8=,iv:UbIqup450wzoAf1faDQfL39Emi97FZ54ZpIcqfe9dhs=,tag:hw2kpKQmR2I3kNK8N5QX/Q==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From d0b3a0f27cc58cd7bc186f09aaef9d03b66dfecc Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 5 Mar 2026 10:34:54 +0000 Subject: [PATCH 075/108] chore(secrets): removed secret "qbittorrent" from machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 5b235d4..4ca8a76 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -23,9 +23,6 @@ mydia: oidc_secret: ENC[AES256_GCM,data:PgI4hmP/3wt9uj+1QvCYcT8Wav0hgCRADouzWM3V695SSfXfbwDgez8tA/tm1/1jymAU2F2sZH8G2hZ1cdHyHQ==,iv:h3o3jsTmnoNE3+mGX12J3ZU0/6PlQNjdndEvaj/czj0=,tag:p3+p4E8fBtR7a8UpM8cUsg==,type:str] secret_key_base: ENC[AES256_GCM,data:yG7HJ5r74Qtxbeyf8F6dA0uHv2pQ8YAJKlKiKjS+m24JRvJWQaTThJ+c5HbuUa6R3e9XtVHchhlVPkF0Is/b+g==,iv:v65xdRr4JdKZmBtjZ08/J3LLqnphSGt9QfVPNQ2x/xg=,tag:n7tD2dhr4IJn1LWM9WW8UA==,type:str] guardian_secret: ENC[AES256_GCM,data:OjnNFSHlecL+qXwlhTm++itRM6ga5E5KrSJxbgIUpbMEkIWgu3xhRtnPdipXbedgall0XdO/s+jnWCagZX94BA==,iv:DukdKvm9vey8BWUiml20tgA/Vji1XVX4+sUPge9nTk0=,tag:q3HdvgUYqR0APiaFz0ul5Q==,type:str] -qbittorrent: - password_hash: ENC[AES256_GCM,data:XPax8MLYtyRRBUIcH2Q36VSGh+0mtdt1GBAWnKsacOS0q1vIVaqek64w7Dcav/y1vNtIC6NeE1odCO1UvY8Ed+Y/NDVWSc3L1Sh0ZhcwgF9kqywQ5bBDmIHWF7Mcx+f7MHKjbWtycJGBObRYZnqEs4qeO8gaJUSfz0J2iUM=,iv:86kwmRSOusePAUISxrinoGaSJfAr3cSryM6A3gQBKG4=,tag:WNmVWBTv+vT1hfsKvhzoOg==,type:str] - password: ENC[AES256_GCM,data:0UlgUE3IGShKbP6XdLQ6JCIjvyK7RwzRMP8mN4f2dUUQHXV1b0C2NGk4cy9YLKR7kBicbX0OojKV2k6766tjBHFeHUOfIPEoTv1VpRyoVwe6PQKThHLe4xK+kbWKq+9QXA1IaPr+KDw5odIusBCNPJbrGUOGjK2ROR7jWvWUdLw=,iv:LaQGvFXXNiS20nOj6n9OpUJ5miaDJ5t3zArhYCluGxY=,tag:kIYOVh8H4iGUyiDX8CT2FQ==,type:str] grafana: oidc_id: ENC[AES256_GCM,data:NVdIgCQ6nz4BSUDJYCKyILtK,iv:tcljy9PzC/yyd7TSdngyJt+uh60uXi2PKu47czErbaQ=,tag:zE4q3dD4UQaHIpGeZ1L48Q==,type:str] oidc_secret: ENC[AES256_GCM,data:b7qILK9ZHW2khtM1Hl/KdjCv3Wq6eOo2Ym/cbjcMB8/3Hn2UelpP4K4lFyiV3bn1/GF6Jl5Z7A0EwMybOx0InA==,iv:3HL/7BiyObwT8DmFxzNPI9CdmCH/4j/4oc9x7qBE1k0=,tag:dBhcq1zLKy6N+jp/v42R4A==,type:str] @@ -60,7 +57,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-03-05T10:32:25Z" - mac: ENC[AES256_GCM,data:Z73v4OqURDMyr8ZXs9SeZsjjY8BpH5T/E4fOJE0CAb13l0SD0MyPv7H7vZdhtiOftMk+LxxH48MaAjyC+7RQwJExA17hA+ZDcDT3JtnxX+35aBaZVPspKKHip5LXN1J2z32gcgW1+9qXhahP0H9RtkxckDSEL7zzyvM4Zji14j8=,iv:UbIqup450wzoAf1faDQfL39Emi97FZ54ZpIcqfe9dhs=,tag:hw2kpKQmR2I3kNK8N5QX/Q==,type:str] + lastmodified: "2026-03-05T10:34:53Z" + mac: ENC[AES256_GCM,data:l4TrQsZuFAF4Z5INhuTOB7GbFCKZroLIe4mewK4xkAHgFg8/euvxpjb5iGIP4G8gT/GtxDEx7hPea6rHS7plCEStX7+r6vNBNRIYxoOBjqPTCvA6kfO/kvMQLQKI9KaoVhWt41jM25WB6L27gMtHQXQbBaOojYXXPC0OiSf05d0=,iv:wJQEYuKe9SODI66MffST9FL6CaXIq5+DelVR/D4wqQM=,tag:xbFx85ghCR4rRaSTc3lGQg==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From 2c7890152b87464151eb5969b0e823aec6d8a029 Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 5 Mar 2026 10:38:18 +0000 Subject: [PATCH 076/108] chore(secrets): set secret "qbittorrent/password" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 4ca8a76..e661d8d 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -37,6 +37,8 @@ whisparr: apikey: ENC[AES256_GCM,data:kIGCsd4mszm90PoQMzlSEBKw9Ow0GvP1qdLtwXYKkAb6b65l89v8lMWJ2X1MyD2gJX+P+Bv1F/2BSjUFXErq/UYnp4dAjwKi/ezGCbhjMutDM1FvwFWEHRnR3gjd9uXPWJ8Xhg==,iv:98aPQlcZHJovpnzACDs6RtKblLnHg6wyi+Er5DAowj8=,tag:Tl8jz/pWYWAtBCfoztKdyw==,type:str] coturn: secret: ENC[AES256_GCM,data:5RmLZ7vQIAvIzvax8oNJkImQ6vXR+MZ2eqxaBJCBlccnFC1rP16/6UtausXVf0eWysw+fpMW5yEmUtAdyxQoPiBCK8lziAZBdkekQnAvFouBaWy8WIZt6XRa71P4xDCDGudpMiGwGGNt+R9yylez+azaLrLyJM3481RPohDMoOM=,iv:2P83lgxGtHwYr+ApAdHopVfRWagxWlC+nt53API/SiQ=,tag:Qv+A03BE1QvEqJMtORiQVA==,type:str] +qbittorrent: + password: ENC[AES256_GCM,data:LIDxh0Ni0JgQGWFix/Ihw7IlUPgzMhrMlWNP5LKkAnEM6EoqA9kFwiPeizB0CZ20+vSqRiL9fikBf8qGLA17L7AKh8I4OTFDlpKpMRtRlMq9S5UBEyOqtOMcvkCSf6/qGoORd1KJSlaitZk47SYRuccOpy/2vAvbMRdLm0SYEqc=,iv:tQdN1N9kXoq7OZbR2eYyy50FltsMAAUI4Lr7U4/SpJE=,tag:3ZOLvjHXD7i7WFy1/Ggqtg==,type:str] sops: age: - recipient: age19qfpf980tadguqq44zf6xwvjvl428dyrj46ha3n6aeqddwhtnuqqml7etq @@ -57,7 +59,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-03-05T10:34:53Z" - mac: ENC[AES256_GCM,data:l4TrQsZuFAF4Z5INhuTOB7GbFCKZroLIe4mewK4xkAHgFg8/euvxpjb5iGIP4G8gT/GtxDEx7hPea6rHS7plCEStX7+r6vNBNRIYxoOBjqPTCvA6kfO/kvMQLQKI9KaoVhWt41jM25WB6L27gMtHQXQbBaOojYXXPC0OiSf05d0=,iv:wJQEYuKe9SODI66MffST9FL6CaXIq5+DelVR/D4wqQM=,tag:xbFx85ghCR4rRaSTc3lGQg==,type:str] + lastmodified: "2026-03-05T10:38:18Z" + mac: ENC[AES256_GCM,data:NO8bgiwHyBOIS59XpwSQPUfIOLzcJeXmoUeXk2HI3HWUsZsGwYEilob73cLY74DiW/L6JmMpEAXNGSlyCigNqDREYTm3pFKVDnOAU3CfPxn9q0mmhQLNSXP4CbKKfegOky2QmU85mLhl53vY+WheWxT4hD6oam8Z5cCm1B4J4Yg=,iv:gxZyF/GxDg4q/J8mtV+nBHFNqh4LKTWJ3j/GjxE6YUE=,tag:9t5T/vbT8dvJq7VhvMjwpw==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From 13ca5cadd42aea85fe83f90cc8fb7afe0c9d1395 Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 5 Mar 2026 10:38:20 +0000 Subject: [PATCH 077/108] chore(secrets): set secret "qbittorrent/password_hash" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index e661d8d..005042c 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -39,6 +39,7 @@ coturn: secret: ENC[AES256_GCM,data:5RmLZ7vQIAvIzvax8oNJkImQ6vXR+MZ2eqxaBJCBlccnFC1rP16/6UtausXVf0eWysw+fpMW5yEmUtAdyxQoPiBCK8lziAZBdkekQnAvFouBaWy8WIZt6XRa71P4xDCDGudpMiGwGGNt+R9yylez+azaLrLyJM3481RPohDMoOM=,iv:2P83lgxGtHwYr+ApAdHopVfRWagxWlC+nt53API/SiQ=,tag:Qv+A03BE1QvEqJMtORiQVA==,type:str] qbittorrent: password: ENC[AES256_GCM,data:LIDxh0Ni0JgQGWFix/Ihw7IlUPgzMhrMlWNP5LKkAnEM6EoqA9kFwiPeizB0CZ20+vSqRiL9fikBf8qGLA17L7AKh8I4OTFDlpKpMRtRlMq9S5UBEyOqtOMcvkCSf6/qGoORd1KJSlaitZk47SYRuccOpy/2vAvbMRdLm0SYEqc=,iv:tQdN1N9kXoq7OZbR2eYyy50FltsMAAUI4Lr7U4/SpJE=,tag:3ZOLvjHXD7i7WFy1/Ggqtg==,type:str] + password_hash: ENC[AES256_GCM,data:urufJbSErLqPdU6jLLZk+27fe4k+cKLXcGRGSqroUDdGMzDnhSF+ZWuPxwDlJQR3ws2GnuiEASncwNO/SALKXFDk2V2gsKJ4hsjyiIbsqCwSEFB/XMY0nY/x0xrcIfMVE0HdrNYeQ3zT01Z5jQpSd7wo2M63LaULL/Av498=,iv:tnUVhOgrImKa6iii2hJZn5LKrySM5v47B2zDZMgmUow=,tag:g3xa/4Z+t1Q9Wnd4XzefLg==,type:str] sops: age: - recipient: age19qfpf980tadguqq44zf6xwvjvl428dyrj46ha3n6aeqddwhtnuqqml7etq @@ -59,7 +60,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-03-05T10:38:18Z" - mac: ENC[AES256_GCM,data:NO8bgiwHyBOIS59XpwSQPUfIOLzcJeXmoUeXk2HI3HWUsZsGwYEilob73cLY74DiW/L6JmMpEAXNGSlyCigNqDREYTm3pFKVDnOAU3CfPxn9q0mmhQLNSXP4CbKKfegOky2QmU85mLhl53vY+WheWxT4hD6oam8Z5cCm1B4J4Yg=,iv:gxZyF/GxDg4q/J8mtV+nBHFNqh4LKTWJ3j/GjxE6YUE=,tag:9t5T/vbT8dvJq7VhvMjwpw==,type:str] + lastmodified: "2026-03-05T10:38:19Z" + mac: ENC[AES256_GCM,data:gS6YTRTl6UdOC7Afrj1LrkgA7MWRLF0HNWytfzhkvThLW+JJrHPEhvWiYrsPW1Bm6o2JkKqVP5HfzcuGNIHJySkEQ4HV02BbibtMNiUKqk+voATsWOpo6957bwRJaTbvDvxmzIQ38TSUoj/pt8Z8WTl0hSPAlqNlWYffXX0y8K4=,iv:53R2bKYKiHJi9DTecg7hiuGNb3Kj9rA2U/oPJ+AFO5I=,tag:5uqvmEJCaCS/yNqyt/FPZg==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From a5de9aea3755c5eb9d5529030e460ff0ea0bf32f Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Thu, 5 Mar 2026 11:39:41 +0100 Subject: [PATCH 078/108] feat: add poor mans version of clan vars --- .just/vars.just | 18 ++++++++++++++++-- script/qbittorrent/hash.py | 19 +++++++++++++++++++ script/qbittorrent/password | 3 +++ script/qbittorrent/password_hash | 3 +++ 4 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 script/qbittorrent/hash.py create mode 100644 script/qbittorrent/password create mode 100644 script/qbittorrent/password_hash diff --git a/.just/vars.just b/.just/vars.just index 2c16d1b..7f464fb 100644 --- a/.just/vars.just +++ b/.just/vars.just @@ -1,7 +1,7 @@ set unstable := true set quiet := true -base_path := invocation_directory() / "systems/x86_64-linux" +base_path := justfile_directory() + "/systems/x86_64-linux" _default: just --list vars @@ -25,7 +25,7 @@ edit machine: [doc('Get var by {key} from {machine}')] get machine key: - sops decrypt {{ base_path }}/{{ machine }}/secrets.yml | yq ".$(echo "{{ key }}" | sed -E 's/\//./g')" + sops decrypt {{ base_path }}/{{ machine }}/secrets.yml | yq ".$(echo "{{ key }}" | sed -E 's/\//./g') // \"\"" [doc('Remove var by {key} for {machine}')] remove machine key: @@ -36,6 +36,20 @@ remove machine key: echo "Done" +[doc('Remove var by {key} for {machine}')] +[script] +generate machine: + for key in $(nix eval --apply 'builtins.attrNames' --json ..#nixosConfigurations.{{ machine }}.config.sops.secrets | jq -r '.[]'); do + # Skip if there's no script + [ -f "{{ justfile_directory() }}/script/$key" ] || continue + + # Skip if we already have a value + [ $(just vars get {{ machine }} "$key" | jq -r) ] && continue + + echo "Executing script for $key" + just vars set {{ machine }} "$key" "$(cd -- "$(dirname "{{ justfile_directory() }}/script/$key")" && source "./$(basename $key)")" + done + [script] check: cd .. diff --git a/script/qbittorrent/hash.py b/script/qbittorrent/hash.py new file mode 100644 index 0000000..a92343f --- /dev/null +++ b/script/qbittorrent/hash.py @@ -0,0 +1,19 @@ +#!/usr/bin/bash + +import base64 +import hashlib +import sys +import uuid + +password = sys.argv[1] +salt = uuid.uuid4() +salt_bytes = salt.bytes + +password = str.encode(password) +hashed_password = hashlib.pbkdf2_hmac("sha512", password, salt_bytes, 100000, dklen=64) +b64_salt = base64.b64encode(salt_bytes).decode("utf-8") +b64_password = base64.b64encode(hashed_password).decode("utf-8") +password_string = "@ByteArray({salt}:{password})".format( + salt=b64_salt, password=b64_password +) +print(password_string) diff --git a/script/qbittorrent/password b/script/qbittorrent/password new file mode 100644 index 0000000..85fc69f --- /dev/null +++ b/script/qbittorrent/password @@ -0,0 +1,3 @@ +#!/bin/bash + +pwgen -s 128 1 diff --git a/script/qbittorrent/password_hash b/script/qbittorrent/password_hash new file mode 100644 index 0000000..86ba315 --- /dev/null +++ b/script/qbittorrent/password_hash @@ -0,0 +1,3 @@ +#!/bin/bash + +python ./hash.py "$(just vars get ulmo qbittorrent/password | jq -r)" From 8d46d0d60b1f06ed7510e83e4117e1ab99674417 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Thu, 5 Mar 2026 11:40:40 +0100 Subject: [PATCH 079/108] Update service configs and secrets handling - Add restartUnits and ownership to Matrix and Servarr secrets - Use sops secret for qbittorrent password hash - Refactor Cardigann indexer config in Servarr - Update Caddy plugin version and hash - Add debug output to machine update justfile --- .just/machine.just | 2 + .../services/communication/matrix/default.nix | 14 +- .../nixos/services/media/servarr/default.nix | 137 ++++++++++++++---- .../services/networking/caddy/default.nix | 4 +- 4 files changed, 126 insertions(+), 31 deletions(-) diff --git a/.just/machine.just b/.just/machine.just index cf1c8f7..420197a 100644 --- a/.just/machine.just +++ b/.just/machine.just @@ -7,6 +7,8 @@ [doc('Update target machine')] [no-exit-message] @update machine: + echo "Checking vars" cd .. && just vars _check {{ machine }} + echo "" just assert '-d "../systems/x86_64-linux/{{ machine }}"' "Machine {{ machine }} does not exist, must be one of: $(ls ../systems/x86_64-linux/ | sed ':a;N;$!ba;s/\n/, /g')" nixos-rebuild switch -L --sudo --target-host {{ machine }} --build-host {{ machine }} --flake ..#{{ machine }} --log-format internal-json -v |& nom --json diff --git a/modules/nixos/services/communication/matrix/default.nix b/modules/nixos/services/communication/matrix/default.nix index 21fe777..f20e1ac 100644 --- a/modules/nixos/services/communication/matrix/default.nix +++ b/modules/nixos/services/communication/matrix/default.nix @@ -370,9 +370,17 @@ in { sops = { secrets = { - "synapse/oidc_id" = {}; - "synapse/oidc_secret" = {}; - "coturn/secret" = {}; + "synapse/oidc_id" = { + restartUnits = ["synapse-matrix.service"]; + }; + "synapse/oidc_secret" = { + restartUnits = ["synapse-matrix.service"]; + }; + "coturn/secret" = { + owner = config.systemd.services.coturn.serviceConfig.User; + group = config.systemd.services.coturn.serviceConfig.Group; + restartUnits = ["coturn.service"]; + }; }; templates = { diff --git a/modules/nixos/services/media/servarr/default.nix b/modules/nixos/services/media/servarr/default.nix index e64c6fe..f868313 100644 --- a/modules/nixos/services/media/servarr/default.nix +++ b/modules/nixos/services/media/servarr/default.nix @@ -86,7 +86,7 @@ in { Prefecences.WebUI = { Username = "admin"; - Password_PBKDF2 = "@ByteArray(JpfX3wSUcMolUFD+8AD67w==:fr5kmc6sK9xsCfGW6HkPX2K1lPYHL6g2ncLLwuOVmjphmxkwBJ8pi/XQDsDWzyM/MRh5zPhUld2Xqn8o7BWv3Q==)"; + Password_PBKDF2 = config.sops.secrets."qbittorrent/password_hash".path; }; }; @@ -94,11 +94,13 @@ in { group = "media"; }; - # port is harcoded in nixpkgs module sabnzbd = { enable = true; openFirewall = true; + allowConfigWrite = false; + configFile = lib.mkForce null; + secretFiles = [ config.sops.templates."sabnzbd/config.ini".path ]; @@ -113,6 +115,7 @@ in { servers = { "news.sunnyusenet.com" = { + name = "news.sunnyusenet.com"; displayname = "news.sunnyusenet.com"; host = "news.sunnyusenet.com"; port = 563; @@ -227,7 +230,6 @@ in { host = "localhost"; username = "admin"; password = lib.tfRef "var.qbittorrent_api_key"; - # password = "poChieN5feeph0igeaCadeJ9Xux0ohmuy6ruH5ieThaPheib3iuzoo0ahw1aiceif1feegioh9Aimau0pai5thoh5ieH0aechohw"; url_base = "/"; port = 2008; }; @@ -270,47 +272,126 @@ in { priority = 1; name = "Nyaa"; - implementation = "nyaa"; - config_contract = "nyaa_settings"; + implementation = "Cardigann"; + config_contract = "CardigannSettings"; protocol = "torrent"; fields = [ { - name = "targetType"; - value = ""; + name = "definitionFile"; + text_value = "nyaasi"; + } + { + name = "baseSettings.limitsUnit"; + number_value = 0; + } + { + name = "torrentBaseSettings.preferMagnetUrl"; + bool_value = false; + } + { + name = "prefer_magnet_links"; + bool_value = true; + } + { + name = "sonarr_compatibility"; + bool_value = false; + } + { + name = "strip_s01"; + bool_value = false; + } + { + name = "radarr_compatibility"; + bool_value = false; + } + { + name = "filter-id"; + number_value = 0; + } + { + name = "cat-id"; + number_value = 0; + } + { + name = "sort"; + number_value = 0; + } + { + name = "type"; + number_value = 1; } ]; }; - "nzbgeek" = { - enable = true; + # "_1337x" = { + # enable = true; - app_profile_id = 2; - priority = 1; + # app_profile_id = 1; + # priority = 1; - name = "NZBgeek"; - implementation = "nzbgeek"; - config_contract = "nzbgeek_settings"; - protocol = "torrent"; + # name = "1337x"; + # implementation = "Cardigann"; + # config_contract = "CardigannSettings"; + # protocol = "torrent"; + # tags = [1]; - fields = [ - ]; - }; + # fields = [ + # { + # name = "definitionFile"; + # text_value = "1337x"; + # } + # { + # name = "baseSettings.limitsUnit"; + # number_value = 0; + # } + # { + # name = "torrentBaseSettings.preferMagnetUrl"; + # bool_value = false; + # } + # { + # name = "disablesort"; + # bool_value = false; + # } + # { + # name = "sort"; + # number_value = 2; + # } + # { + # name = "type"; + # number_value = 1; + # } + # ]; + # }; # "nzbgeek" = { # enable = true; - # app_profile_id = 1; + # app_profile_id = 2; + # priority = 1; + # name = "NZBgeek"; - # implementation = "nzbgeek"; - # config_contract = "nzbgeek_settings"; - # protocol = "torrent"; + # implementation = "Newznab"; + # config_contract = "NewznabSettings"; + # protocol = "usenet"; # fields = [ - # # { - # # name = ""; - # # value = ""; - # # } + # { + # name = "baseUrl"; + # text_value = "https://api.nzbgeek.info"; + # } + # { + # name = "apiPath"; + # text_value = "/api"; + # } + # { + # name = "apiKey"; + # text_value = "__TODO_API_KEY_SECRET__"; + # } + # { + # name = "baseSettings.limitsUnit"; + # number_value = 5; + # } # ]; # }; }; @@ -421,6 +502,10 @@ in { { secrets = { "qbittorrent/password" = {}; + "qbittorrent/password_hash" = { + owner = "qbittorrent"; + group = "media"; + }; "sabnzbd/apikey" = {}; "sabnzbd/nzbkey" = {}; "sabnzbd/sunnyweb/username" = {}; diff --git a/modules/nixos/services/networking/caddy/default.nix b/modules/nixos/services/networking/caddy/default.nix index 2274efa..f17c737 100644 --- a/modules/nixos/services/networking/caddy/default.nix +++ b/modules/nixos/services/networking/caddy/default.nix @@ -28,8 +28,8 @@ in { enable = cfg.enable; package = pkgs.caddy.withPlugins { - plugins = ["github.com/corazawaf/coraza-caddy/v2@2.1.0"]; - hash = ""; + plugins = ["github.com/corazawaf/coraza-caddy/v2@v2.1.0"]; + hash = "sha256-AdL/LFKXbWmCsJ/xZWZmYBnw57c7sS6s1miR3sSx1Ow="; }; virtualHosts = From a4671a524faba0ff8db11dd5d11b67b2d264d287 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Sun, 22 Mar 2026 16:41:08 +0100 Subject: [PATCH 080/108] kaas --- modules/home/desktop/plasma/panels.nix | 4 +-- modules/nixos/application/steam/default.nix | 5 ++++ modules/nixos/services/games/openrct.nix | 27 +++++++++++++++++++++ packages/studio/default.nix | 4 +-- systems/x86_64-linux/manwe/default.nix | 2 +- 5 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 modules/nixos/services/games/openrct.nix diff --git a/modules/home/desktop/plasma/panels.nix b/modules/home/desktop/plasma/panels.nix index 52212b7..6e20938 100644 --- a/modules/home/desktop/plasma/panels.nix +++ b/modules/home/desktop/plasma/panels.nix @@ -95,7 +95,7 @@ digitalClock = { date = { enable = true; - format = "shortDate"; + format.custom = "dd-MM-yyyy"; position = "belowTime"; }; time = { @@ -106,4 +106,4 @@ } ]; } -] \ No newline at end of file +] diff --git a/modules/nixos/application/steam/default.nix b/modules/nixos/application/steam/default.nix index fc42935..061765e 100644 --- a/modules/nixos/application/steam/default.nix +++ b/modules/nixos/application/steam/default.nix @@ -23,6 +23,11 @@ in { remotePlay.openFirewall = true; dedicatedServer.openFirewall = true; localNetworkGameTransfers.openFirewall = true; + + extraCompatPackages = with pkgs; [ + proton-ge-bin + ]; + # package = pkgs.steam.override { # extraEnv = { # DXVK_HUD = "compiler"; diff --git a/modules/nixos/services/games/openrct.nix b/modules/nixos/services/games/openrct.nix new file mode 100644 index 0000000..a36f0fb --- /dev/null +++ b/modules/nixos/services/games/openrct.nix @@ -0,0 +1,27 @@ +{ config, lib, pkgs, namespace, ... }: +let + inherit (lib) mkIf mkEnableOption; + + cfg = config.${namespace}.services.games.openrct; +in +{ + options.${namespace}.services.games.openrct = { + enable = mkEnableOption "OpenRCT2"; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + openrct2 + ]; + + systemd.services.openrct = { + enable = true; + after = [ "network.target"]; + description = "OpenRCT2 Server"; + serviceConfig = { + Type = ""; + ExecStart = lib.getExe pkgs.openrct2; + }; + }; + }; +} diff --git a/packages/studio/default.nix b/packages/studio/default.nix index 1e6b457..cb628c9 100644 --- a/packages/studio/default.nix +++ b/packages/studio/default.nix @@ -11,8 +11,8 @@ in mkWindowsAppNoCC rec { inherit wine; - pname = "studio"; - version = "2.25.4"; + pname = "studio"; + version = "2.25.12"; src = fetchurl { url = "https://studio.download.bricklink.info/Studio2.0+EarlyAccess/Archive/2.25.12_1/Studio+2.0+EarlyAccess.exe"; diff --git a/systems/x86_64-linux/manwe/default.nix b/systems/x86_64-linux/manwe/default.nix index 179e410..a1b421b 100644 --- a/systems/x86_64-linux/manwe/default.nix +++ b/systems/x86_64-linux/manwe/default.nix @@ -8,7 +8,7 @@ services.logrotate.checkConfig = false; - environment.systemPackages = with pkgs; [ beyond-all-reason ]; + environment.systemPackages = with pkgs; [ beyond-all-reason openrct2 ]; sneeuwvlok = { hardware.has = { From df41d59ae1287ccd88bc092fd9d7cff8323aad44 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Sun, 22 Mar 2026 16:43:14 +0100 Subject: [PATCH 081/108] chore: update dependencies --- flake.lock | 208 ++++++++++++++++++++++++----------------------------- 1 file changed, 93 insertions(+), 115 deletions(-) diff --git a/flake.lock b/flake.lock index 5faa0c0..4cc9f95 100644 --- a/flake.lock +++ b/flake.lock @@ -83,11 +83,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1771802456, - "narHash": "sha256-Ku3vdfRr0JBcTbcu8oNSVYNLLDVrIlDXvuYv0qZaJvg=", - "rev": "e1f0211652ba266dc0ca504fe3c4775d8cad16f8", + "lastModified": 1774174479, + "narHash": "sha256-6stwl7hiMK6Jvn11cBnw3TutkVSdPp1ILh+93aWVImA=", + "rev": "a50863e540a43fc0617ecbf8adada90af3899f57", "type": "tarball", - "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/e1f0211652ba266dc0ca504fe3c4775d8cad16f8.tar.gz" + "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/a50863e540a43fc0617ecbf8adada90af3899f57.tar.gz" }, "original": { "type": "tarball", @@ -125,11 +125,11 @@ ] }, "locked": { - "lastModified": 1771586574, - "narHash": "sha256-Nzay8rHhCrlFaIiDqlTpEiKZZTUOQsdZJ8wdB+lrJro=", - "rev": "17da134c02b2e92e10ffcbcb4870e5cde0a6c6f7", + "lastModified": 1774087718, + "narHash": "sha256-UU4KzRMTFJttIoSnRm1SWheFcfAVAsNqG+4JauKib3g=", + "rev": "734047b2dd1e67c3a803999777cdf749f3199342", "type": "tarball", - "url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/17da134c02b2e92e10ffcbcb4870e5cde0a6c6f7.tar.gz" + "url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/734047b2dd1e67c3a803999777cdf749f3199342.tar.gz" }, "original": { "type": "tarball", @@ -144,11 +144,11 @@ ] }, "locked": { - "lastModified": 1771469470, - "narHash": "sha256-GnqdqhrguKNN3HtVfl6z+zbV9R9jhHFm3Z8nu7R6ml0=", + "lastModified": 1773889306, + "narHash": "sha256-PAqwnsBSI9SVC2QugvQ3xeYCB0otOwCacB1ueQj2tgw=", "owner": "nix-community", "repo": "disko", - "rev": "4707eec8d1d2db5182ea06ed48c820a86a42dc13", + "rev": "5ad85c82cc52264f4beddc934ba57f3789f28347", "type": "github" }, "original": { @@ -160,15 +160,14 @@ "erosanix": { "inputs": { "flake-compat": "flake-compat", - "nixpkgs": "nixpkgs", - "tiny-audio-player": "tiny-audio-player" + "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1771529616, - "narHash": "sha256-FiVKf4ZSHCcHOKkQAaIcjQGWiTnlepv5462Djk10BeY=", + "lastModified": 1773767380, + "narHash": "sha256-fHrKh0/EQlEJe6czXPo9/bw1lki7w0RAGKRqYv/445s=", "owner": "emmanuelrosa", "repo": "erosanix", - "rev": "ed5217725bf19acfb594be8a4a653e3f576a3397", + "rev": "ada69cf31f7649f8e59fe5376c94f3b0ea38bf37", "type": "github" }, "original": { @@ -185,11 +184,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1771743970, - "narHash": "sha256-eri4eY0fUouYxBgWxJAJzG+xTGXVI7VeNJGcJrqpEt0=", + "lastModified": 1774163246, + "narHash": "sha256-gzlqyLjP44LWraUd3Zn4xrQKOtK+zcBJ77pnsSUsxcM=", "owner": "nix-community", "repo": "fenix", - "rev": "2af8ae8bbe91833a54bd3b9cc24c326b66972a8e", + "rev": "4cd28929c68cae521589bc21958d3793904ed1e2", "type": "github" }, "original": { @@ -205,11 +204,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1771811831, - "narHash": "sha256-adtW0jeSg/uZ6anL1mhK+kHAPpYR1+X5kmL6ZtDrQkw=", + "lastModified": 1774141843, + "narHash": "sha256-gpjHyyfLvBLZQiWumOxsfsOxt6KTjNhUOXk+m9ISBHc=", "owner": "nix-community", "repo": "flake-firefox-nightly", - "rev": "0cd9d065adab3b7d12747ba54cbf0e9b4154351f", + "rev": "3a1fcd6a4dbd617ad2014dd03aa68cdd885d5322", "type": "github" }, "original": { @@ -321,11 +320,11 @@ ] }, "locked": { - "lastModified": 1769996383, - "narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=", + "lastModified": 1772408722, + "narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "57928607ea566b5db3ad13af0e57e921e6b12381", + "rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3", "type": "github" }, "original": { @@ -552,11 +551,11 @@ ] }, "locked": { - "lastModified": 1771538633, - "narHash": "sha256-MBA5xFLd4dXdNwCYintpO7yBm2xj92PagsNmYpw+tSg=", + "lastModified": 1773992301, + "narHash": "sha256-lm1qy9P463cblBAFC2g8VaALR1Gje1oyYXCPtiEumus=", "owner": "himmelblau-idm", "repo": "himmelblau", - "rev": "a734234d38833fc4d0522e79e308daf99bd5f1e1", + "rev": "fcb8966990c24f97fe224fa0c8977fe730d4cf50", "type": "github" }, "original": { @@ -572,11 +571,11 @@ ] }, "locked": { - "lastModified": 1771756436, - "narHash": "sha256-Tl2I0YXdhSTufGqAaD1ySh8x+cvVsEI1mJyJg12lxhI=", + "lastModified": 1774135471, + "narHash": "sha256-TVeIGOxnfSPM6JvkRkXHpJECnj1OG2dXkWMSA4elzzQ=", "owner": "nix-community", "repo": "home-manager", - "rev": "5bd3589390b431a63072868a90c0f24771ff4cbb", + "rev": "856b01ebd1de3f53c3929ce8082d9d67d799d816", "type": "github" }, "original": { @@ -593,11 +592,11 @@ ] }, "locked": { - "lastModified": 1771756436, - "narHash": "sha256-Tl2I0YXdhSTufGqAaD1ySh8x+cvVsEI1mJyJg12lxhI=", + "lastModified": 1773422513, + "narHash": "sha256-MPjR48roW7CUMU6lu0+qQGqj92Kuh3paIulMWFZy+NQ=", "owner": "nix-community", "repo": "home-manager", - "rev": "5bd3589390b431a63072868a90c0f24771ff4cbb", + "rev": "ef12a9a2b0f77c8fa3dda1e7e494fca668909056", "type": "github" }, "original": { @@ -614,11 +613,11 @@ ] }, "locked": { - "lastModified": 1771587792, - "narHash": "sha256-XGFLdlLOez7f0rmjlF+1TLXyBguy8gx2aBHx/Q5JXxs=", + "lastModified": 1774168156, + "narHash": "sha256-+pwZSARdlM2RQQ6V0q76+WMKW9aNIcxkSOIThcz/f0A=", "owner": "Jovian-Experiments", "repo": "Jovian-NixOS", - "rev": "b49fc54950e251f166a2240799315033ab7a8916", + "rev": "939caad56508542d0f19cab963e2bc693f5f2831", "type": "github" }, "original": { @@ -633,11 +632,11 @@ "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1771765102, - "narHash": "sha256-RLvOaBEoxgPnGZn9ULbb6xXs98AgiOyPZQpB44XyLvA=", + "lastModified": 1773579712, + "narHash": "sha256-cvxFTYuOvvmpLJz5nB8iREmMGsDksY6gmZFf74UKD1Q=", "owner": "nix-community", "repo": "lib-aggregate", - "rev": "55efa4ba1ddbbe046a4afd17b51867c5348bdce8", + "rev": "c23c52797845b8e4f273ddb5ccdf8622b5d98284", "type": "github" }, "original": { @@ -729,11 +728,11 @@ ] }, "locked": { - "lastModified": 1771520882, - "narHash": "sha256-9SeTZ4Pwr730YfT7V8Azb8GFbwk1ZwiQDAwft3qAD+o=", + "lastModified": 1773000227, + "narHash": "sha256-zm3ftUQw0MPumYi91HovoGhgyZBlM4o3Zy0LhPNwzXE=", "owner": "nix-darwin", "repo": "nix-darwin", - "rev": "6a7fdcd5839ec8b135821179eea3b58092171bcf", + "rev": "da529ac9e46f25ed5616fd634079a5f3c579135f", "type": "github" }, "original": { @@ -771,11 +770,11 @@ "systems": "systems_3" }, "locked": { - "lastModified": 1771641457, - "narHash": "sha256-TIekRGfeCwuEmYcWex40RTx0Gd46pqmyUtxdFKb5juI=", + "lastModified": 1774060651, + "narHash": "sha256-sZiam+rmNcOZGnlbnqDD9oTwfMdQUM+uQmFqqSoe194=", "owner": "Infinidoge", "repo": "nix-minecraft", - "rev": "c4e2b8969e09067da9d44b6b5762e1e896418f40", + "rev": "46727bd27d32d63069ed26a690554373ae2b4702", "type": "github" }, "original": { @@ -856,11 +855,11 @@ ] }, "locked": { - "lastModified": 1771563879, - "narHash": "sha256-vA5hocvdGhr+jfBN7A7ogeZqIz2qx01EixXwdVsQcnE=", + "lastModified": 1773882647, + "narHash": "sha256-VzcOcE0LLpEnyoxLuMuptZ9ZWCkSBn99bTgEQoz5Viw=", "owner": "nix-community", "repo": "nixos-wsl", - "rev": "379d20c55f552e91fb9f3f0382e4a97d3f452943", + "rev": "fd0eae98d1ecee31024271f8d64676250a386ee7", "type": "github" }, "original": { @@ -871,11 +870,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1769914019, - "narHash": "sha256-w3TySosUsTuVdWAoHEVxvPIX42lCv/98Rmt5LRu3Bw8=", + "lastModified": 1772380631, + "narHash": "sha256-FhW0uxeXjefINP0vUD4yRBB52Us7fXZPk9RiPAopfiY=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "17317ace7fb805f192bcf5595e41d18b09f9b497", + "rev": "6d3b61b190a899042ce82a5355111976ba76d698", "type": "github" }, "original": { @@ -887,11 +886,11 @@ }, "nixpkgs-lib": { "locked": { - "lastModified": 1771723719, - "narHash": "sha256-e+/T/pmEkLP6BHhYjx6GmwP5ivonQQn0bJdH9YrRB+Q=", + "lastModified": 1773538553, + "narHash": "sha256-hohiyWALn8cXqk3FPnE3UADy03lRMaTV5iRzKCU86zM=", "owner": "nix-community", "repo": "nixpkgs.lib", - "rev": "36b8fcb216736b0e1869740b324ae521e5df23d8", + "rev": "a5ed666a3c206de0019b4c9dafc3a51f352bc7e3", "type": "github" }, "original": { @@ -902,11 +901,11 @@ }, "nixpkgs_10": { "locked": { - "lastModified": 1771207753, - "narHash": "sha256-b9uG8yN50DRQ6A7JdZBfzq718ryYrlmGgqkRm9OOwCE=", + "lastModified": 1773840656, + "narHash": "sha256-9tpvMGFteZnd3gRQZFlRCohVpqooygFuy9yjuyRL2C0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "d1c15b7d5806069da59e819999d70e1cec0760bf", + "rev": "9cf7092bdd603554bd8b63c216e8943cf9b12512", "type": "github" }, "original": { @@ -934,11 +933,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1771742218, - "narHash": "sha256-ofVOq6pFrLkIE6YanvUDElZJRwjSSJaTuilqhdnatMA=", + "lastModified": 1774106199, + "narHash": "sha256-US5Tda2sKmjrg2lNHQL3jRQ6p96cgfWh3J1QBliQ8Ws=", "owner": "nixos", "repo": "nixpkgs", - "rev": "aaf43e7c58bb8093a6325ef1d7b4af616779abc5", + "rev": "6c9a78c09ff4d6c21d0319114873508a6ec01655", "type": "github" }, "original": { @@ -981,11 +980,11 @@ }, "nixpkgs_5": { "locked": { - "lastModified": 1771829251, - "narHash": "sha256-aCGm04/IRKKAy9qzvSOjSOkcYmNEjaoClo/9FygDp2Y=", + "lastModified": 1774192834, + "narHash": "sha256-Ro1L12XoZiA63+JOskKf/w49v8K8hQDkEvNqem7nnik=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "cb31c55b2ba66c33f94d251251c37802ff5b1dab", + "rev": "116515096225d29ffa1b6d576dd04b93941fe591", "type": "github" }, "original": { @@ -1029,11 +1028,11 @@ }, "nixpkgs_8": { "locked": { - "lastModified": 1771369470, - "narHash": "sha256-0NBlEBKkN3lufyvFegY4TYv5mCNHbi5OmBDrzihbBMQ=", + "lastModified": 1773821835, + "narHash": "sha256-TJ3lSQtW0E2JrznGVm8hOQGVpXjJyXY2guAxku2O9A4=", "owner": "nixos", "repo": "nixpkgs", - "rev": "0182a361324364ae3f436a63005877674cf45efb", + "rev": "b40629efe5d6ec48dd1efba650c797ddbd39ace0", "type": "github" }, "original": { @@ -1094,11 +1093,11 @@ "systems": "systems_4" }, "locked": { - "lastModified": 1771704400, - "narHash": "sha256-8U9xnN4HdxPfAXAft3lBsArWSv1ZTTxJci1lOA/xpno=", + "lastModified": 1774134539, + "narHash": "sha256-VTbmIpAP4OlM76uwUUezfewBUsrfWk2l3H2QaTY6QLc=", "owner": "notashelf", "repo": "nvf", - "rev": "5c38b357da7e8c870350cd1847fb5b2602a28eb0", + "rev": "85ca579065a079ee9ee603339668c7c16b61c4f7", "type": "github" }, "original": { @@ -1117,11 +1116,11 @@ ] }, "locked": { - "lastModified": 1770766818, - "narHash": "sha256-12RCFLyAedyMOdenUi7cN3ioJPEGjA/ZG1BLjugfUVs=", + "lastModified": 1772361940, + "narHash": "sha256-B1Cz+ydL1iaOnGlwOFld/C8lBECPtzhiy/pP93/CuyY=", "owner": "nix-community", "repo": "plasma-manager", - "rev": "44b928068359b7d2310a34de39555c63c93a2c90", + "rev": "a4b33606111c9c5dcd10009042bb710307174f51", "type": "github" }, "original": { @@ -1159,11 +1158,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1771639390, - "narHash": "sha256-igbphgls7JmrblWCIbgBGcL/ZWj0Iv+InySvuhLC5Ew=", + "lastModified": 1774097238, + "narHash": "sha256-hcujm/qEX4RUybdBCrQKdQNqTRYDItmnbjJRP5ky5vc=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "af68fc6e782f218c262a8e7e5718ce7276f697a2", + "rev": "76de1de27c0ca1329bc41324edab22c82d69e779", "type": "github" }, "original": { @@ -1203,11 +1202,11 @@ ] }, "locked": { - "lastModified": 1771735105, - "narHash": "sha256-MJuVJeszZEziquykEHh/hmgIHYxUcuoG/1aowpLiSeU=", + "lastModified": 1774154798, + "narHash": "sha256-zsTuloDSdKf+PrI1MsWx5z/cyGEJ8P3eERtAfdP8Bmg=", "owner": "Mic92", "repo": "sops-nix", - "rev": "d7755d820f5fa8acf7f223309c33e25d4f92e74f", + "rev": "3e0d543e6ba6c0c48117a81614e90c6d8c425170", "type": "github" }, "original": { @@ -1221,11 +1220,11 @@ "nixpkgs": "nixpkgs_10" }, "locked": { - "lastModified": 1771735105, - "narHash": "sha256-MJuVJeszZEziquykEHh/hmgIHYxUcuoG/1aowpLiSeU=", + "lastModified": 1774154798, + "narHash": "sha256-zsTuloDSdKf+PrI1MsWx5z/cyGEJ8P3eERtAfdP8Bmg=", "owner": "Mic92", "repo": "sops-nix", - "rev": "d7755d820f5fa8acf7f223309c33e25d4f92e74f", + "rev": "3e0d543e6ba6c0c48117a81614e90c6d8c425170", "type": "github" }, "original": { @@ -1253,11 +1252,11 @@ "tinted-zed": "tinted-zed" }, "locked": { - "lastModified": 1771787992, - "narHash": "sha256-Vg4bGwwenNYI8p3nJTl9FRyeIyrjATeZrZr+GyUSDrw=", + "lastModified": 1774124764, + "narHash": "sha256-Poz9WTjiRlqZIf197CrMMJfTifZhrZpbHFv0eU1Nhtg=", "owner": "nix-community", "repo": "stylix", - "rev": "30054cca073b49b42a71289edec858f535b27fe9", + "rev": "e31c79f571c5595a155f84b9d77ce53a84745494", "type": "github" }, "original": { @@ -1380,11 +1379,11 @@ "systems": "systems_7" }, "locked": { - "lastModified": 1771504637, - "narHash": "sha256-qPYBCcvws0cqVf4blYyxQ6JNxOdvUPK41s2sfqk6wL0=", + "lastModified": 1773700838, + "narHash": "sha256-6KFxpxyXjcqhOexc7ZeaXVWdDtGb6zO8HtjBEci9DfU=", "owner": "terranix", "repo": "terranix", - "rev": "f3d77064bd135823a30916a1e63b90b7fe4453ac", + "rev": "306ce146bf0324dc3b3c45c095036b6f0e26bf35", "type": "github" }, "original": { @@ -1474,27 +1473,6 @@ "type": "github" } }, - "tiny-audio-player": { - "inputs": { - "nixpkgs": [ - "erosanix", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1771529133, - "narHash": "sha256-nnd13UkxEGBNCJUpSinNyoDfB1BjhSGnWN8llDM9AW8=", - "owner": "emmanuelrosa", - "repo": "tiny_audio_player", - "rev": "21b191dce6be77dcf0f5baa69564b7e33905c653", - "type": "github" - }, - "original": { - "owner": "emmanuelrosa", - "repo": "tiny_audio_player", - "type": "github" - } - }, "treefmt-nix": { "inputs": { "nixpkgs": [ @@ -1503,11 +1481,11 @@ ] }, "locked": { - "lastModified": 1770228511, - "narHash": "sha256-wQ6NJSuFqAEmIg2VMnLdCnUc0b7vslUohqqGGD+Fyxk=", + "lastModified": 1773297127, + "narHash": "sha256-6E/yhXP7Oy/NbXtf1ktzmU8SdVqJQ09HC/48ebEGBpk=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "337a4fe074be1042a35086f15481d763b8ddc0e7", + "rev": "71b125cd05fbfd78cab3e070b73544abe24c5016", "type": "github" }, "original": { @@ -1524,11 +1502,11 @@ ] }, "locked": { - "lastModified": 1771829403, - "narHash": "sha256-y6SCyTHx3mfeJphVAP9IcYwmd81l7Owv1WObibVcexw=", + "lastModified": 1774155194, + "narHash": "sha256-0+8XV5WPO5Ie8hBcEEpPoR7mCqUmMnVZFiu6DQIxIE0=", "owner": "0xc000022070", "repo": "zen-browser-flake", - "rev": "16e6705c152f28f380aac601c705fbe905a58b44", + "rev": "56e6e71b465967758ff4db948ff943cb8ea31ca4", "type": "github" }, "original": { From 793866e621aa3e9bcc14148f0cee58e20070764f Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Mon, 9 Mar 2026 11:34:06 +0100 Subject: [PATCH 082/108] Refactor var generation and update service configs - Refactor var generation scripts to use _rotate helper - Update Glance service URLs to use configured ports - Set static password hash for qBittorrent in Servarr config - Update Caddy plugin hash - Remove oauth_auto_login from Grafana config - Add shared pwgen script for password generation --- .just/vars.just | 19 ++++++++++++------- .../nixos/services/media/glance/default.nix | 10 +++++----- .../nixos/services/media/servarr/default.nix | 5 ++++- .../services/networking/caddy/default.nix | 2 +- .../observability/grafana/default.nix | 1 - script/.shared/pwgen | 3 +++ 6 files changed, 25 insertions(+), 15 deletions(-) create mode 100644 script/.shared/pwgen diff --git a/.just/vars.just b/.just/vars.just index 7f464fb..62a8bd9 100644 --- a/.just/vars.just +++ b/.just/vars.just @@ -36,20 +36,25 @@ remove machine key: echo "Done" -[doc('Remove var by {key} for {machine}')] +[doc('Generate var values for {machine}')] [script] generate machine: for key in $(nix eval --apply 'builtins.attrNames' --json ..#nixosConfigurations.{{ machine }}.config.sops.secrets | jq -r '.[]'); do - # Skip if there's no script - [ -f "{{ justfile_directory() }}/script/$key" ] || continue - # Skip if we already have a value - [ $(just vars get {{ machine }} "$key" | jq -r) ] && continue + [ $(just vars get "{{ machine }}" "$key" | jq -r) ] && continue - echo "Executing script for $key" - just vars set {{ machine }} "$key" "$(cd -- "$(dirname "{{ justfile_directory() }}/script/$key")" && source "./$(basename $key)")" + just _rotate "{{ machine }}" "$key" done +[doc('Regenerate var values for {machine}')] +[script] +_rotate machine key: + # Exit if there's no script + [ -f "{{ justfile_directory() }}/script/{{ key }}" ] || exit + + echo "Executing script for {{ key }}" + just vars set "{{ machine }}" "{{ key }}" "$(cd -- "$(dirname "{{ justfile_directory() }}/script/{{ key }}")" && source "./$(basename "{{ key }}")")" + [script] check: cd .. diff --git a/modules/nixos/services/media/glance/default.nix b/modules/nixos/services/media/glance/default.nix index 6af52ef..c9da350 100644 --- a/modules/nixos/services/media/glance/default.nix +++ b/modules/nixos/services/media/glance/default.nix @@ -100,22 +100,22 @@ in { } { title = "Radarr"; - url = "http://${config.networking.hostName}:2001"; + url = "http://${config.networking.hostName}:${builtins.toString config.services.radarr.settings.server.port}"; icon = "sh:radarr"; } { title = "Sonarr"; - url = "http://${config.networking.hostName}:2002"; + url = "http://${config.networking.hostName}:${builtins.toString config.services.sonarr.settings.server.port}"; icon = "sh:sonarr"; } { title = "Lidarr"; - url = "http://${config.networking.hostName}:2003"; + url = "http://${config.networking.hostName}:${builtins.toString config.services.lidarr.settings.server.port}"; icon = "sh:lidarr"; } { title = "Prowlarr"; - url = "http://${config.networking.hostName}:2004"; + url = "http://${config.networking.hostName}:${builtins.toString config.services.prowlarr.settings.server.port}"; icon = "sh:prowlarr"; } { @@ -125,7 +125,7 @@ in { } { title = "SABnzbd"; - url = "http://${config.networking.hostName}:8080"; + url = "http://${config.networking.hostName}:${builtins.toString config.services.sabnzbd.settings.misc.port}"; icon = "sh:sabnzbd"; } ]; diff --git a/modules/nixos/services/media/servarr/default.nix b/modules/nixos/services/media/servarr/default.nix index f868313..6953421 100644 --- a/modules/nixos/services/media/servarr/default.nix +++ b/modules/nixos/services/media/servarr/default.nix @@ -85,8 +85,11 @@ in { LegalNotice.Accepted = true; Prefecences.WebUI = { + AlternativeUIEnabled = true; + RootFolder = "''${pkgs.vuetorrent}/share/vuetorrent"; + Username = "admin"; - Password_PBKDF2 = config.sops.secrets."qbittorrent/password_hash".path; + Password_PBKDF2 = "@ByteArray(Yhyk8fzgSHuKcgcmIxhYzg==:9njltqI5znb98+n+eOqUvpe4xYj6Dcub994o2fe9kpTa1fczMdHf/fNoifLaGmEf69xkTNSztEuh6BqcR4/CbQ==)"; #config.sops.secrets."qbittorrent/password_hash".path; }; }; diff --git a/modules/nixos/services/networking/caddy/default.nix b/modules/nixos/services/networking/caddy/default.nix index f17c737..4cab016 100644 --- a/modules/nixos/services/networking/caddy/default.nix +++ b/modules/nixos/services/networking/caddy/default.nix @@ -29,7 +29,7 @@ in { package = pkgs.caddy.withPlugins { plugins = ["github.com/corazawaf/coraza-caddy/v2@v2.1.0"]; - hash = "sha256-AdL/LFKXbWmCsJ/xZWZmYBnw57c7sS6s1miR3sSx1Ow="; + hash = "sha256-rsDnTunR8C7hVOX5aKcba+iFYHbpWek65DZgbMxOdTs="; }; virtualHosts = diff --git a/modules/nixos/services/observability/grafana/default.nix b/modules/nixos/services/observability/grafana/default.nix index e2040d4..a867351 100644 --- a/modules/nixos/services/observability/grafana/default.nix +++ b/modules/nixos/services/observability/grafana/default.nix @@ -36,7 +36,6 @@ in { auth = { disable_login_form = false; - oauth_auto_login = true; }; "auth.basic".enable = false; diff --git a/script/.shared/pwgen b/script/.shared/pwgen new file mode 100644 index 0000000..85fc69f --- /dev/null +++ b/script/.shared/pwgen @@ -0,0 +1,3 @@ +#!/bin/bash + +pwgen -s 128 1 From 5b844aab8d76969079dbd43cbc89c53ed58e48d2 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Mon, 23 Mar 2026 08:24:31 +0100 Subject: [PATCH 083/108] . --- .../nixos/services/media/glance/default.nix | 6 +++ .../services/networking/wireguard/default.nix | 47 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 modules/nixos/services/networking/wireguard/default.nix diff --git a/modules/nixos/services/media/glance/default.nix b/modules/nixos/services/media/glance/default.nix index c9da350..ec6e851 100644 --- a/modules/nixos/services/media/glance/default.nix +++ b/modules/nixos/services/media/glance/default.nix @@ -13,6 +13,12 @@ in { }; config = mkIf cfg.enable { + ${namespace}.services.networking.caddy.hosts = { + "https://${config.networking.hostName}:443" = '' + reverse_proxy http://[::]:2000 + ''; + }; + services.glance = { enable = true; openFirewall = true; diff --git a/modules/nixos/services/networking/wireguard/default.nix b/modules/nixos/services/networking/wireguard/default.nix new file mode 100644 index 0000000..92bd803 --- /dev/null +++ b/modules/nixos/services/networking/wireguard/default.nix @@ -0,0 +1,47 @@ +{ + config, + pkgs, + lib, + namespace, + ... +}: let + inherit (builtins) length; + inherit (lib) mkIf mkEnableOption mkOption types attrNames attrsToList listToAttrs; + + cfg = config.${namespace}.services.networking.wireguard; + hasPeers = (cfg.peer |> attrNames |> length) > 0; +in { + options.${namespace}.services.networking.wireguard = { + # enable = mkEnableOption "enable wireguard" // {default = true;}; + + peer = mkOption { + type = types.attrsOf (types.submodule { + options = { + port = mkOption { + type = types.port; + description = ''''; + }; + + address = mkOption { + type = types.listOf types.str; + default = []; + description = ''''; + }; + }; + }); + }; + }; + + config = mkIf hasPeers { + networking.firewall.allowedUDPPorts = cfg.peer |> lib.attrValues |> lib.map (p: p.port); + networking.wq-quick = { + # enable = cfg.enable; + + interfaces = + cfg.peer + |> attrsToList + |> imap0 (i: { name, value }: (namevaluepair "wg${i}" (value // { })); + |> listToAttrs + }; + }; +} From 4fd0b16db0fab33baab4dc403b867b893878ab60 Mon Sep 17 00:00:00 2001 From: chris Date: Mon, 23 Mar 2026 08:46:20 +0000 Subject: [PATCH 084/108] chore: update dependencies --- flake.lock | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/flake.lock b/flake.lock index 4cc9f95..e2ef3a5 100644 --- a/flake.lock +++ b/flake.lock @@ -83,11 +83,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1774174479, - "narHash": "sha256-6stwl7hiMK6Jvn11cBnw3TutkVSdPp1ILh+93aWVImA=", - "rev": "a50863e540a43fc0617ecbf8adada90af3899f57", + "lastModified": 1774210137, + "narHash": "sha256-QaPn/8NlrXd6jd8S9+KV2pYsGNZ8KWU5+jv2/QtRlUw=", + "rev": "1862f2641e54a51755b0b9acb907d01f6b324b2a", "type": "tarball", - "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/a50863e540a43fc0617ecbf8adada90af3899f57.tar.gz" + "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/1862f2641e54a51755b0b9acb907d01f6b324b2a.tar.gz" }, "original": { "type": "tarball", @@ -184,11 +184,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1774163246, - "narHash": "sha256-gzlqyLjP44LWraUd3Zn4xrQKOtK+zcBJ77pnsSUsxcM=", + "lastModified": 1774250935, + "narHash": "sha256-mWID0WFgTnd9hbEeaPNX+YYWF70JN3r7zBouEqERJOE=", "owner": "nix-community", "repo": "fenix", - "rev": "4cd28929c68cae521589bc21958d3793904ed1e2", + "rev": "64d7705e8c37d650cfb1aa99c24a8ce46597f29e", "type": "github" }, "original": { @@ -571,11 +571,11 @@ ] }, "locked": { - "lastModified": 1774135471, - "narHash": "sha256-TVeIGOxnfSPM6JvkRkXHpJECnj1OG2dXkWMSA4elzzQ=", + "lastModified": 1774210133, + "narHash": "sha256-yeiWCY9aAUUJ3ebMVjs0UZXRnT5x90MCtpbpOWiXrvM=", "owner": "nix-community", "repo": "home-manager", - "rev": "856b01ebd1de3f53c3929ce8082d9d67d799d816", + "rev": "c6fe2944ad9f2444b2d767c4a5edee7c166e8a95", "type": "github" }, "original": { @@ -980,11 +980,11 @@ }, "nixpkgs_5": { "locked": { - "lastModified": 1774192834, - "narHash": "sha256-Ro1L12XoZiA63+JOskKf/w49v8K8hQDkEvNqem7nnik=", + "lastModified": 1774253681, + "narHash": "sha256-U3LMRHov4wQ4olZq/zvf94Qf7oL6W11fjvZGvWg3gZc=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "116515096225d29ffa1b6d576dd04b93941fe591", + "rev": "16b430b0e3a5233df0444f14928af915555308ac", "type": "github" }, "original": { @@ -1028,11 +1028,11 @@ }, "nixpkgs_8": { "locked": { - "lastModified": 1773821835, - "narHash": "sha256-TJ3lSQtW0E2JrznGVm8hOQGVpXjJyXY2guAxku2O9A4=", + "lastModified": 1774106199, + "narHash": "sha256-US5Tda2sKmjrg2lNHQL3jRQ6p96cgfWh3J1QBliQ8Ws=", "owner": "nixos", "repo": "nixpkgs", - "rev": "b40629efe5d6ec48dd1efba650c797ddbd39ace0", + "rev": "6c9a78c09ff4d6c21d0319114873508a6ec01655", "type": "github" }, "original": { @@ -1093,11 +1093,11 @@ "systems": "systems_4" }, "locked": { - "lastModified": 1774134539, - "narHash": "sha256-VTbmIpAP4OlM76uwUUezfewBUsrfWk2l3H2QaTY6QLc=", + "lastModified": 1774224548, + "narHash": "sha256-g45WZAZHNc7wJBkK4IdB5dq0Bh0JE7G0gcY2H5DFi44=", "owner": "notashelf", "repo": "nvf", - "rev": "85ca579065a079ee9ee603339668c7c16b61c4f7", + "rev": "edfb73fa4ced576f587d259a70a513b4152f8cea", "type": "github" }, "original": { @@ -1158,11 +1158,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1774097238, - "narHash": "sha256-hcujm/qEX4RUybdBCrQKdQNqTRYDItmnbjJRP5ky5vc=", + "lastModified": 1774221325, + "narHash": "sha256-aEIdkqB8gtQZtEbogdUb5iyfcZpKIlD3FkG8ANu73/I=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "76de1de27c0ca1329bc41324edab22c82d69e779", + "rev": "b42b63f390a4dab14e6efa34a70e67f5b087cc62", "type": "github" }, "original": { @@ -1502,11 +1502,11 @@ ] }, "locked": { - "lastModified": 1774155194, - "narHash": "sha256-0+8XV5WPO5Ie8hBcEEpPoR7mCqUmMnVZFiu6DQIxIE0=", + "lastModified": 1774242250, + "narHash": "sha256-pchbnY7KVnH26g4O3LZO8vpshInqNj937gAqlPob1Mk=", "owner": "0xc000022070", "repo": "zen-browser-flake", - "rev": "56e6e71b465967758ff4db948ff943cb8ea31ca4", + "rev": "f19c3e6683c2d2f3fcfcb88fb691931a104bc47c", "type": "github" }, "original": { From 18ebde767e607ac7a07e5d7acd642876a001ad34 Mon Sep 17 00:00:00 2001 From: chris Date: Mon, 23 Mar 2026 10:07:33 +0000 Subject: [PATCH 085/108] chore: update dependencies --- flake.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/flake.lock b/flake.lock index e2ef3a5..c9df8ee 100644 --- a/flake.lock +++ b/flake.lock @@ -83,11 +83,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1774210137, - "narHash": "sha256-QaPn/8NlrXd6jd8S9+KV2pYsGNZ8KWU5+jv2/QtRlUw=", - "rev": "1862f2641e54a51755b0b9acb907d01f6b324b2a", + "lastModified": 1774258552, + "narHash": "sha256-wTJJxhLPr3OHXQ23H9+Ch1YjdlaoMf3605ezfRYLaC4=", + "rev": "28bb98f5aec0ea70b623ab4953eb8186acdb7bba", "type": "tarball", - "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/1862f2641e54a51755b0b9acb907d01f6b324b2a.tar.gz" + "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/28bb98f5aec0ea70b623ab4953eb8186acdb7bba.tar.gz" }, "original": { "type": "tarball", @@ -980,11 +980,11 @@ }, "nixpkgs_5": { "locked": { - "lastModified": 1774253681, - "narHash": "sha256-U3LMRHov4wQ4olZq/zvf94Qf7oL6W11fjvZGvWg3gZc=", + "lastModified": 1774259547, + "narHash": "sha256-5EQ1TL+R/tcsoGas1oALp5Tj2ACfSul+pfrrxP72xC0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "16b430b0e3a5233df0444f14928af915555308ac", + "rev": "b3f8d82c4c685fb6f3080745dab8f07606ae50d3", "type": "github" }, "original": { From 478f26a9b8c13aa3d7b9199a76fd47fc64628759 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Mon, 23 Mar 2026 11:27:46 +0100 Subject: [PATCH 086/108] . --- .../services/networking/caddy/default.nix | 2 +- .../services/networking/wireguard/default.nix | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/nixos/services/networking/caddy/default.nix b/modules/nixos/services/networking/caddy/default.nix index 4cab016..e18a707 100644 --- a/modules/nixos/services/networking/caddy/default.nix +++ b/modules/nixos/services/networking/caddy/default.nix @@ -29,7 +29,7 @@ in { package = pkgs.caddy.withPlugins { plugins = ["github.com/corazawaf/coraza-caddy/v2@v2.1.0"]; - hash = "sha256-rsDnTunR8C7hVOX5aKcba+iFYHbpWek65DZgbMxOdTs="; + hash = "sha256-pSXjLaZoRtKV3eFl2ySRSjl3yxi514G1Cb7pfrpxxtE="; }; virtualHosts = diff --git a/modules/nixos/services/networking/wireguard/default.nix b/modules/nixos/services/networking/wireguard/default.nix index 92bd803..0cf5320 100644 --- a/modules/nixos/services/networking/wireguard/default.nix +++ b/modules/nixos/services/networking/wireguard/default.nix @@ -33,15 +33,15 @@ in { }; config = mkIf hasPeers { - networking.firewall.allowedUDPPorts = cfg.peer |> lib.attrValues |> lib.map (p: p.port); - networking.wq-quick = { - # enable = cfg.enable; + # networking.firewall.allowedUDPPorts = cfg.peer |> lib.attrValues |> lib.map (p: p.port); + # networking.wq-quick = { + # # enable = cfg.enable; - interfaces = - cfg.peer - |> attrsToList - |> imap0 (i: { name, value }: (namevaluepair "wg${i}" (value // { })); - |> listToAttrs - }; + # interfaces = + # cfg.peer + # |> attrsToList + # |> imap0 (i: { name, value }: (namevaluepair "wg${i}" (value // {}))) + # |> listToAttrs; + # }; }; } From 2bbbe034447cac485b2dd04723b4f8de8ab28f58 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Mon, 23 Mar 2026 12:36:32 +0100 Subject: [PATCH 087/108] . --- .../authentication/zitadel/default.nix | 4 ++-- .../services/communication/matrix/default.nix | 4 ++-- .../services/networking/caddy/default.nix | 14 +++++++---- .../observability/promtail/default.nix | 23 ++++++++++++------- 4 files changed, 29 insertions(+), 16 deletions(-) diff --git a/modules/nixos/services/authentication/zitadel/default.nix b/modules/nixos/services/authentication/zitadel/default.nix index 082330e..7674835 100644 --- a/modules/nixos/services/authentication/zitadel/default.nix +++ b/modules/nixos/services/authentication/zitadel/default.nix @@ -543,12 +543,12 @@ in networking.caddy = { hosts = { "auth.kruining.eu" = '' - reverse_proxy h2c://::1:9092 + reverse_proxy h2c://[::1]:9092 ''; }; extraConfig = '' (auth) { - forward_auth h2c://::1:9092 { + forward_auth h2c://[::1]:9092 { uri /api/authz/forward-auth copy_headers Remote-User Remote-Groups Remote-Email Remote-Name } diff --git a/modules/nixos/services/communication/matrix/default.nix b/modules/nixos/services/communication/matrix/default.nix index f20e1ac..d2e47b0 100644 --- a/modules/nixos/services/communication/matrix/default.nix +++ b/modules/nixos/services/communication/matrix/default.nix @@ -89,8 +89,8 @@ in { ''; "${fqn}" = '' - reverse_proxy /_matrix/* http://::1:${toString port} - reverse_proxy /_synapse/client/* http://::1:${toString port} + reverse_proxy /_matrix/* http://[::1]:${toString port} + reverse_proxy /_synapse/client/* http://[::1]:${toString port} ''; }; }; diff --git a/modules/nixos/services/networking/caddy/default.nix b/modules/nixos/services/networking/caddy/default.nix index e18a707..ec9df3a 100644 --- a/modules/nixos/services/networking/caddy/default.nix +++ b/modules/nixos/services/networking/caddy/default.nix @@ -10,6 +10,15 @@ cfg = config.${namespace}.services.networking.caddy; hasHosts = (cfg.hosts |> attrNames |> length) > 0; + caddyBase = pkgs.callPackage "${pkgs.path}/pkgs/by-name/ca/caddy/package.nix" { + buildGo125Module = pkgs.buildGo126Module; + caddy = caddyBase; + }; + caddyPackage = + caddyBase.withPlugins { + plugins = ["github.com/corazawaf/coraza-caddy/v2@v2.1.0"]; + hash = "sha256-pSXjLaZoRtKV3eFl2ySRSjl3yxi514G1Cb7pfrpxxtE="; + }; in { options.${namespace}.services.networking.caddy = { enable = mkEnableOption "enable caddy" // {default = true;}; @@ -27,10 +36,7 @@ in { services.caddy = { enable = cfg.enable; - package = pkgs.caddy.withPlugins { - plugins = ["github.com/corazawaf/coraza-caddy/v2@v2.1.0"]; - hash = "sha256-pSXjLaZoRtKV3eFl2ySRSjl3yxi514G1Cb7pfrpxxtE="; - }; + package = caddyPackage; virtualHosts = cfg.hosts diff --git a/modules/nixos/services/observability/promtail/default.nix b/modules/nixos/services/observability/promtail/default.nix index 25aabbd..38dbbab 100644 --- a/modules/nixos/services/observability/promtail/default.nix +++ b/modules/nixos/services/observability/promtail/default.nix @@ -1,11 +1,15 @@ -{ pkgs, config, lib, namespace, ... }: -let +{ + pkgs, + config, + lib, + namespace, + ... +}: let inherit (lib.modules) mkIf; inherit (lib.options) mkEnableOption; cfg = config.${namespace}.services.observability.promtail; -in -{ +in { options.${namespace}.services.observability.promtail = { enable = mkEnableOption "enable Grafana Promtail"; }; @@ -31,7 +35,7 @@ in clients = [ { - url = "http://::1:9003/loki/api/v1/push"; + url = "http://[::1]:9003/loki/api/v1/push"; } ]; @@ -46,13 +50,16 @@ in }; }; relabel_configs = [ - { source_labels = [ "__journal__systemd_unit" ]; target_label = "unit"; } + { + source_labels = ["__journal__systemd_unit"]; + target_label = "unit"; + } ]; } ]; }; }; - - networking.firewall.allowedTCPPorts = [ 9004 ]; + + networking.firewall.allowedTCPPorts = [9004]; }; } From 51adeb02e609496408d1a3f57fc4a6926b811bc9 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Mon, 23 Mar 2026 15:09:58 +0100 Subject: [PATCH 088/108] fix a load of stuff --- .just/machine.just | 8 +++- .../nixos/services/media/servarr/default.nix | 45 +++++++++++++------ 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/.just/machine.just b/.just/machine.just index 420197a..3cb4587 100644 --- a/.just/machine.just +++ b/.just/machine.just @@ -11,4 +11,10 @@ cd .. && just vars _check {{ machine }} echo "" just assert '-d "../systems/x86_64-linux/{{ machine }}"' "Machine {{ machine }} does not exist, must be one of: $(ls ../systems/x86_64-linux/ | sed ':a;N;$!ba;s/\n/, /g')" - nixos-rebuild switch -L --sudo --target-host {{ machine }} --build-host {{ machine }} --flake ..#{{ machine }} --log-format internal-json -v |& nom --json + nixos-rebuild switch -L --sudo --target-host {{ machine }} --flake ..#{{ machine }} --log-format internal-json -v |& nom --json + +[doc('Check if target machine builds')] +[no-exit-message] +@check machine: + just assert '-d "../systems/x86_64-linux/{{ machine }}"' "Machine {{ machine }} does not exist, must be one of: $(ls ../systems/x86_64-linux/ | sed ':a;N;$!ba;s/\n/, /g')" + nix build ..#nixosConfigurations.{{ machine }}.config.system.build.toplevel diff --git a/modules/nixos/services/media/servarr/default.nix b/modules/nixos/services/media/servarr/default.nix index 6953421..ba03076 100644 --- a/modules/nixos/services/media/servarr/default.nix +++ b/modules/nixos/services/media/servarr/default.nix @@ -80,18 +80,7 @@ in { enable = true; openFirewall = true; webuiPort = 2008; - - serverConfig = { - LegalNotice.Accepted = true; - - Prefecences.WebUI = { - AlternativeUIEnabled = true; - RootFolder = "''${pkgs.vuetorrent}/share/vuetorrent"; - - Username = "admin"; - Password_PBKDF2 = "@ByteArray(Yhyk8fzgSHuKcgcmIxhYzg==:9njltqI5znb98+n+eOqUvpe4xYj6Dcub994o2fe9kpTa1fczMdHf/fNoifLaGmEf69xkTNSztEuh6BqcR4/CbQ==)"; #config.sops.secrets."qbittorrent/password_hash".path; - }; - }; + serverConfig = lib.mkForce {}; user = "qbittorrent"; group = "media"; @@ -246,7 +235,7 @@ in { host = "localhost"; api_key = lib.tfRef "var.sabnzbd_api_key"; url_base = "/"; - port = 8080; + port = 2009; }; }; } @@ -425,7 +414,7 @@ in { # Sleep for a bit to give the service a chance to start up sleep 5s - if [ "$(systemctl is-active ${service})" != "active" ]; then + if [ "$(systemctl is-active "${service}")" != "active" ]; then echo "${service} is not running" exit 1 fi @@ -464,6 +453,18 @@ in { })) |> lib.mkMerge; + system.activationScripts.qbittorrent-config = { + deps = lib.optional (!config.sops.useSystemdActivation) "setupSecrets"; + # TODO: If sops-nix is switched to systemd activation, add a systemd unit + # for this install step that runs after sops-install-secrets.service, + # because this activation-script dependency only orders against setupSecrets. + text = '' + install -Dm0600 -o ${config.services.qbittorrent.user} -g ${config.services.qbittorrent.group} \ + ${config.sops.templates."qbittorrent/qBittorrent.conf".path} \ + ${config.services.qbittorrent.profileDir}/qBittorrent/config/qBittorrent.conf + ''; + }; + users = cfg |> lib.mapAttrsToList (service: {enable, ...}: (mkIf enable { @@ -532,6 +533,22 @@ in { sabnzbd_api_key = "${config.sops.placeholder."sabnzbd/apikey"}" ''; }; + "qbittorrent/qBittorrent.conf" = { + owner = "qbittorrent"; + group = "media"; + mode = "0600"; + restartUnits = ["qbittorrent.service"]; + content = '' + [LegalNotice] + Accepted=true + + [Preferences] + WebUI\AlternativeUIEnabled=true + WebUI\RootFolder=${pkgs.vuetorrent}/share/vuetorrent + WebUI\Username=admin + WebUI\Password_PBKDF2=${config.sops.placeholder."qbittorrent/password_hash"} + ''; + }; "sabnzbd/config.ini" = { owner = "sabnzbd"; group = "media"; From 01fb98ba10ebc098edcbe256b9766650297bd911 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Tue, 24 Mar 2026 07:30:41 +0100 Subject: [PATCH 089/108] various fixes --- .../nixos/services/media/glance/default.nix | 2 +- .../nixos/services/media/servarr/default.nix | 73 +------------------ systems/x86_64-linux/orome/default.nix | 10 ++- 3 files changed, 11 insertions(+), 74 deletions(-) diff --git a/modules/nixos/services/media/glance/default.nix b/modules/nixos/services/media/glance/default.nix index ec6e851..b042297 100644 --- a/modules/nixos/services/media/glance/default.nix +++ b/modules/nixos/services/media/glance/default.nix @@ -15,7 +15,7 @@ in { config = mkIf cfg.enable { ${namespace}.services.networking.caddy.hosts = { "https://${config.networking.hostName}:443" = '' - reverse_proxy http://[::]:2000 + reverse_proxy http://[::1]:2000 ''; }; diff --git a/modules/nixos/services/media/servarr/default.nix b/modules/nixos/services/media/servarr/default.nix index ba03076..c7a066c 100644 --- a/modules/nixos/services/media/servarr/default.nix +++ b/modules/nixos/services/media/servarr/default.nix @@ -99,7 +99,9 @@ in { settings = { misc = { + host = "0.0.0.0"; port = 2009; + host_whitelist = "${config.networking.hostName}"; download_dir = "/var/media/downloads/incomplete"; complete_dir = "/var/media/downloads/done"; @@ -315,77 +317,6 @@ in { } ]; }; - - # "_1337x" = { - # enable = true; - - # app_profile_id = 1; - # priority = 1; - - # name = "1337x"; - # implementation = "Cardigann"; - # config_contract = "CardigannSettings"; - # protocol = "torrent"; - # tags = [1]; - - # fields = [ - # { - # name = "definitionFile"; - # text_value = "1337x"; - # } - # { - # name = "baseSettings.limitsUnit"; - # number_value = 0; - # } - # { - # name = "torrentBaseSettings.preferMagnetUrl"; - # bool_value = false; - # } - # { - # name = "disablesort"; - # bool_value = false; - # } - # { - # name = "sort"; - # number_value = 2; - # } - # { - # name = "type"; - # number_value = 1; - # } - # ]; - # }; - - # "nzbgeek" = { - # enable = true; - - # app_profile_id = 2; - # priority = 1; - - # name = "NZBgeek"; - # implementation = "Newznab"; - # config_contract = "NewznabSettings"; - # protocol = "usenet"; - - # fields = [ - # { - # name = "baseUrl"; - # text_value = "https://api.nzbgeek.info"; - # } - # { - # name = "apiPath"; - # text_value = "/api"; - # } - # { - # name = "apiKey"; - # text_value = "__TODO_API_KEY_SECRET__"; - # } - # { - # name = "baseSettings.limitsUnit"; - # number_value = 5; - # } - # ]; - # }; }; } ] diff --git a/systems/x86_64-linux/orome/default.nix b/systems/x86_64-linux/orome/default.nix index 48e049b..e155461 100644 --- a/systems/x86_64-linux/orome/default.nix +++ b/systems/x86_64-linux/orome/default.nix @@ -1,16 +1,22 @@ -{ ... }: -{ +{pkgs, ...}: { imports = [ ./disks.nix ./hardware.nix ]; + environment.systemPackages = with pkgs; [ + azure-cli + github-copilot-cli + ]; + sneeuwvlok = { hardware.has = { bluetooth = true; audio = true; }; + authentication.himmelblau.enable = true; + application = { steam.enable = true; }; From a10e74a5964cefcf5e4a7b50d6f504678a88f4c5 Mon Sep 17 00:00:00 2001 From: chris Date: Sun, 5 Apr 2026 12:36:49 +0000 Subject: [PATCH 090/108] chore: update dependencies --- flake.lock | 246 ++++++++++++++++++++++++----------------------------- 1 file changed, 113 insertions(+), 133 deletions(-) diff --git a/flake.lock b/flake.lock index c9df8ee..757ab1e 100644 --- a/flake.lock +++ b/flake.lock @@ -83,11 +83,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1774258552, - "narHash": "sha256-wTJJxhLPr3OHXQ23H9+Ch1YjdlaoMf3605ezfRYLaC4=", - "rev": "28bb98f5aec0ea70b623ab4953eb8186acdb7bba", + "lastModified": 1775389026, + "narHash": "sha256-cHYF7eGiVqgEnIQKs105eV0P5/zOvxl443qO1f5/Bps=", + "rev": "d53f3c0b42400ff608dd468ac33359881baf969e", "type": "tarball", - "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/28bb98f5aec0ea70b623ab4953eb8186acdb7bba.tar.gz" + "url": "https://git.clan.lol/api/v1/repos/clan/clan-core/archive/d53f3c0b42400ff608dd468ac33359881baf969e.tar.gz" }, "original": { "type": "tarball", @@ -125,11 +125,11 @@ ] }, "locked": { - "lastModified": 1774087718, - "narHash": "sha256-UU4KzRMTFJttIoSnRm1SWheFcfAVAsNqG+4JauKib3g=", - "rev": "734047b2dd1e67c3a803999777cdf749f3199342", + "lastModified": 1774796937, + "narHash": "sha256-uDcgnNHK1D2oTHOQKsqQUPdDGMuG94dp3Nv8LsnqkEM=", + "rev": "04e10e10c7b4bbf2930f24d139326707a43cbb54", "type": "tarball", - "url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/734047b2dd1e67c3a803999777cdf749f3199342.tar.gz" + "url": "https://git.clan.lol/api/v1/repos/clan/data-mesher/archive/04e10e10c7b4bbf2930f24d139326707a43cbb54.tar.gz" }, "original": { "type": "tarball", @@ -163,11 +163,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1773767380, - "narHash": "sha256-fHrKh0/EQlEJe6czXPo9/bw1lki7w0RAGKRqYv/445s=", + "lastModified": 1775241072, + "narHash": "sha256-YpXDFEkd+JjxZOgTnvt5GHvEhORxkAda9Lc1e8e8Ox8=", "owner": "emmanuelrosa", "repo": "erosanix", - "rev": "ada69cf31f7649f8e59fe5376c94f3b0ea38bf37", + "rev": "14ac50e5ddefdb1c5ed66c11d2c6fa68959d690a", "type": "github" }, "original": { @@ -184,11 +184,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1774250935, - "narHash": "sha256-mWID0WFgTnd9hbEeaPNX+YYWF70JN3r7zBouEqERJOE=", + "lastModified": 1775373929, + "narHash": "sha256-Elx3es3UvLova3YBdJTc9rju9ULl9+5XF4K5t5Ejsa8=", "owner": "nix-community", "repo": "fenix", - "rev": "64d7705e8c37d650cfb1aa99c24a8ce46597f29e", + "rev": "221468471f762f355db24ce728012544561650f5", "type": "github" }, "original": { @@ -204,11 +204,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1774141843, - "narHash": "sha256-gpjHyyfLvBLZQiWumOxsfsOxt6KTjNhUOXk+m9ISBHc=", + "lastModified": 1775388520, + "narHash": "sha256-WUnKn7L/yBo7a5xH2UmPvBfYUr3d4Q8EPCz5r09C8Eo=", "owner": "nix-community", "repo": "flake-firefox-nightly", - "rev": "3a1fcd6a4dbd617ad2014dd03aa68cdd885d5322", + "rev": "00070174d7a635f5238aee06e4feb481ccc7d9f9", "type": "github" }, "original": { @@ -220,11 +220,11 @@ "firefox-gnome-theme": { "flake": false, "locked": { - "lastModified": 1764873433, - "narHash": "sha256-1XPewtGMi+9wN9Ispoluxunw/RwozuTRVuuQOmxzt+A=", + "lastModified": 1775176642, + "narHash": "sha256-2veEED0Fg7Fsh81tvVDNYR6SzjqQxa7hbi18Jv4LWpM=", "owner": "rafaelmardojai", "repo": "firefox-gnome-theme", - "rev": "f7ffd917ac0d253dbd6a3bf3da06888f57c69f92", + "rev": "179704030c5286c729b5b0522037d1d51341022c", "type": "github" }, "original": { @@ -320,11 +320,11 @@ ] }, "locked": { - "lastModified": 1772408722, - "narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=", + "lastModified": 1775087534, + "narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3", + "rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b", "type": "github" }, "original": { @@ -383,11 +383,11 @@ ] }, "locked": { - "lastModified": 1767609335, - "narHash": "sha256-feveD98mQpptwrAEggBQKJTYbvwwglSbOv53uCfH9PY=", + "lastModified": 1775087534, + "narHash": "sha256-91qqW8lhL7TLwgQWijoGBbiD4t7/q75KTi8NxjVmSmA=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "250481aafeb741edfe23d29195671c19b36b6dca", + "rev": "3107b77cd68437b9a76194f0f7f9c55f2329ca5b", "type": "github" }, "original": { @@ -510,20 +510,18 @@ "gnome-shell": { "flake": false, "locked": { - "host": "gitlab.gnome.org", "lastModified": 1767737596, "narHash": "sha256-eFujfIUQDgWnSJBablOuG+32hCai192yRdrNHTv0a+s=", "owner": "GNOME", "repo": "gnome-shell", "rev": "ef02db02bf0ff342734d525b5767814770d85b49", - "type": "gitlab" + "type": "github" }, "original": { - "host": "gitlab.gnome.org", "owner": "GNOME", - "ref": "gnome-49", "repo": "gnome-shell", - "type": "gitlab" + "rev": "ef02db02bf0ff342734d525b5767814770d85b49", + "type": "github" } }, "grub2-themes": { @@ -551,11 +549,11 @@ ] }, "locked": { - "lastModified": 1773992301, - "narHash": "sha256-lm1qy9P463cblBAFC2g8VaALR1Gje1oyYXCPtiEumus=", + "lastModified": 1775230022, + "narHash": "sha256-FBhkbsqDTULYB1nS92y1CT7qSAM9rUMZR9hS8AvIw24=", "owner": "himmelblau-idm", "repo": "himmelblau", - "rev": "fcb8966990c24f97fe224fa0c8977fe730d4cf50", + "rev": "d700f39281354c0b08cfb9640011a381bed29136", "type": "github" }, "original": { @@ -571,11 +569,11 @@ ] }, "locked": { - "lastModified": 1774210133, - "narHash": "sha256-yeiWCY9aAUUJ3ebMVjs0UZXRnT5x90MCtpbpOWiXrvM=", + "lastModified": 1775360939, + "narHash": "sha256-XUBlSgUFdvTh6+K5LcI5mJu5F5L8scmJDMRiZM484TM=", "owner": "nix-community", "repo": "home-manager", - "rev": "c6fe2944ad9f2444b2d767c4a5edee7c166e8a95", + "rev": "2097a5c82bdc099c6135eae4b111b78124604554", "type": "github" }, "original": { @@ -592,11 +590,11 @@ ] }, "locked": { - "lastModified": 1773422513, - "narHash": "sha256-MPjR48roW7CUMU6lu0+qQGqj92Kuh3paIulMWFZy+NQ=", + "lastModified": 1774991950, + "narHash": "sha256-kScKj3qJDIWuN9/6PMmgy5esrTUkYinrO5VvILik/zw=", "owner": "nix-community", "repo": "home-manager", - "rev": "ef12a9a2b0f77c8fa3dda1e7e494fca668909056", + "rev": "f2d3e04e278422c7379e067e323734f3e8c585a7", "type": "github" }, "original": { @@ -613,11 +611,11 @@ ] }, "locked": { - "lastModified": 1774168156, - "narHash": "sha256-+pwZSARdlM2RQQ6V0q76+WMKW9aNIcxkSOIThcz/f0A=", + "lastModified": 1775287496, + "narHash": "sha256-tCBlt+RP85MLrMYntro/YvG7NWktbmFiyItGBo85Tf8=", "owner": "Jovian-Experiments", "repo": "Jovian-NixOS", - "rev": "939caad56508542d0f19cab963e2bc693f5f2831", + "rev": "0a7a3feb77606db451aa10287ad4c4c8f85922f8", "type": "github" }, "original": { @@ -632,11 +630,11 @@ "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1773579712, - "narHash": "sha256-cvxFTYuOvvmpLJz5nB8iREmMGsDksY6gmZFf74UKD1Q=", + "lastModified": 1774789463, + "narHash": "sha256-MFraiT8o6manIcEloazGYafji1ua3HJ7Re/A/uauqYA=", "owner": "nix-community", "repo": "lib-aggregate", - "rev": "c23c52797845b8e4f273ddb5ccdf8622b5d98284", + "rev": "dc3bd444a2ea0834374b7d759c532f232e144128", "type": "github" }, "original": { @@ -728,11 +726,11 @@ ] }, "locked": { - "lastModified": 1773000227, - "narHash": "sha256-zm3ftUQw0MPumYi91HovoGhgyZBlM4o3Zy0LhPNwzXE=", + "lastModified": 1775037210, + "narHash": "sha256-KM2WYj6EA7M/FVZVCl3rqWY+TFV5QzSyyGE2gQxeODU=", "owner": "nix-darwin", "repo": "nix-darwin", - "rev": "da529ac9e46f25ed5616fd634079a5f3c579135f", + "rev": "06648f4902343228ce2de79f291dd5a58ee12146", "type": "github" }, "original": { @@ -770,11 +768,11 @@ "systems": "systems_3" }, "locked": { - "lastModified": 1774060651, - "narHash": "sha256-sZiam+rmNcOZGnlbnqDD9oTwfMdQUM+uQmFqqSoe194=", + "lastModified": 1775359538, + "narHash": "sha256-PbX+bT49p9c7cmT03ufao8tDDEn0Qi7R82R1yXDyk5k=", "owner": "Infinidoge", "repo": "nix-minecraft", - "rev": "46727bd27d32d63069ed26a690554373ae2b4702", + "rev": "bdf703935b0aa47d9de1c6a7536fc76756b044ef", "type": "github" }, "original": { @@ -855,11 +853,11 @@ ] }, "locked": { - "lastModified": 1773882647, - "narHash": "sha256-VzcOcE0LLpEnyoxLuMuptZ9ZWCkSBn99bTgEQoz5Viw=", + "lastModified": 1774972752, + "narHash": "sha256-DnLIpFxznohpLkIFs390uZ0gxwkVyhtknhKNu+lQJK8=", "owner": "nix-community", "repo": "nixos-wsl", - "rev": "fd0eae98d1ecee31024271f8d64676250a386ee7", + "rev": "d97e078f4788cddb8d11c3c99f72a4bb9ddec221", "type": "github" }, "original": { @@ -870,11 +868,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1772380631, - "narHash": "sha256-FhW0uxeXjefINP0vUD4yRBB52Us7fXZPk9RiPAopfiY=", + "lastModified": 1775054576, + "narHash": "sha256-iiIr1hlTMu2LLARsUYtiqlE90tqocqIMVLK2fIzB/UY=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "6d3b61b190a899042ce82a5355111976ba76d698", + "rev": "fc4b9b74d4b0bdbf3c97fef4bd34c05225172912", "type": "github" }, "original": { @@ -886,11 +884,11 @@ }, "nixpkgs-lib": { "locked": { - "lastModified": 1773538553, - "narHash": "sha256-hohiyWALn8cXqk3FPnE3UADy03lRMaTV5iRzKCU86zM=", + "lastModified": 1774748309, + "narHash": "sha256-+U7gF3qxzwD5TZuANzZPeJTZRHS29OFQgkQ2kiTJBIQ=", "owner": "nix-community", "repo": "nixpkgs.lib", - "rev": "a5ed666a3c206de0019b4c9dafc3a51f352bc7e3", + "rev": "333c4e0545a6da976206c74db8773a1645b5870a", "type": "github" }, "original": { @@ -901,11 +899,11 @@ }, "nixpkgs_10": { "locked": { - "lastModified": 1773840656, - "narHash": "sha256-9tpvMGFteZnd3gRQZFlRCohVpqooygFuy9yjuyRL2C0=", + "lastModified": 1775126147, + "narHash": "sha256-J0dZU4atgcfo4QvM9D92uQ0Oe1eLTxBVXjJzdEMQpD0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9cf7092bdd603554bd8b63c216e8943cf9b12512", + "rev": "8d8c1fa5b412c223ffa47410867813290cdedfef", "type": "github" }, "original": { @@ -917,11 +915,11 @@ }, "nixpkgs_11": { "locked": { - "lastModified": 1767767207, - "narHash": "sha256-Mj3d3PfwltLmukFal5i3fFt27L6NiKXdBezC1EBuZs4=", + "lastModified": 1775036866, + "narHash": "sha256-ZojAnPuCdy657PbTq5V0Y+AHKhZAIwSIT2cb8UgAz/U=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "5912c1772a44e31bf1c63c0390b90501e5026886", + "rev": "6201e203d09599479a3b3450ed24fa81537ebc4e", "type": "github" }, "original": { @@ -933,11 +931,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1774106199, - "narHash": "sha256-US5Tda2sKmjrg2lNHQL3jRQ6p96cgfWh3J1QBliQ8Ws=", + "lastModified": 1775371993, + "narHash": "sha256-shlcgEOzW6rl7zmZeYBMP9EpF3O/cTL7/HpWlyqearw=", "owner": "nixos", "repo": "nixpkgs", - "rev": "6c9a78c09ff4d6c21d0319114873508a6ec01655", + "rev": "ff2af6f7ebc6c123603d5689aeea6461290f46b5", "type": "github" }, "original": { @@ -980,11 +978,11 @@ }, "nixpkgs_5": { "locked": { - "lastModified": 1774259547, - "narHash": "sha256-5EQ1TL+R/tcsoGas1oALp5Tj2ACfSul+pfrrxP72xC0=", + "lastModified": 1775391773, + "narHash": "sha256-8h0YBzKR6kf+68qnZtZnC6GhTf2XAilTQ9F/tm5JDWs=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "b3f8d82c4c685fb6f3080745dab8f07606ae50d3", + "rev": "728629d3d4797ab52406df91b319c07a7d2ce479", "type": "github" }, "original": { @@ -1028,11 +1026,11 @@ }, "nixpkgs_8": { "locked": { - "lastModified": 1774106199, - "narHash": "sha256-US5Tda2sKmjrg2lNHQL3jRQ6p96cgfWh3J1QBliQ8Ws=", + "lastModified": 1775036866, + "narHash": "sha256-ZojAnPuCdy657PbTq5V0Y+AHKhZAIwSIT2cb8UgAz/U=", "owner": "nixos", "repo": "nixpkgs", - "rev": "6c9a78c09ff4d6c21d0319114873508a6ec01655", + "rev": "6201e203d09599479a3b3450ed24fa81537ebc4e", "type": "github" }, "original": { @@ -1044,11 +1042,11 @@ }, "nixpkgs_9": { "locked": { - "lastModified": 1771008912, - "narHash": "sha256-gf2AmWVTs8lEq7z/3ZAsgnZDhWIckkb+ZnAo5RzSxJg=", + "lastModified": 1774386573, + "narHash": "sha256-4hAV26quOxdC6iyG7kYaZcM3VOskcPUrdCQd/nx8obc=", "owner": "nixos", "repo": "nixpkgs", - "rev": "a82ccc39b39b621151d6732718e3e250109076fa", + "rev": "46db2e09e1d3f113a13c0d7b81e2f221c63b8ce9", "type": "github" }, "original": { @@ -1070,11 +1068,11 @@ ] }, "locked": { - "lastModified": 1767810917, - "narHash": "sha256-ZKqhk772+v/bujjhla9VABwcvz+hB2IaRyeLT6CFnT0=", + "lastModified": 1775228139, + "narHash": "sha256-ebbeHmg+V7w8050bwQOuhmQHoLOEOfqKzM1KgCTexK4=", "owner": "nix-community", "repo": "NUR", - "rev": "dead29c804adc928d3a69dfe7f9f12d0eec1f1a4", + "rev": "601971b9c89e0304561977f2c28fa25e73aa7132", "type": "github" }, "original": { @@ -1093,11 +1091,11 @@ "systems": "systems_4" }, "locked": { - "lastModified": 1774224548, - "narHash": "sha256-g45WZAZHNc7wJBkK4IdB5dq0Bh0JE7G0gcY2H5DFi44=", + "lastModified": 1775122065, + "narHash": "sha256-ZlowJNkQOhpsXDuWbHgB1xY6W8kyzYn9coK9nJsqqNg=", "owner": "notashelf", "repo": "nvf", - "rev": "edfb73fa4ced576f587d259a70a513b4152f8cea", + "rev": "d3304af3d5771e8d5bac6ee9bbdbce56086d54f7", "type": "github" }, "original": { @@ -1116,11 +1114,11 @@ ] }, "locked": { - "lastModified": 1772361940, - "narHash": "sha256-B1Cz+ydL1iaOnGlwOFld/C8lBECPtzhiy/pP93/CuyY=", + "lastModified": 1774915545, + "narHash": "sha256-COT4l/+ZddGBvrDVfPf7MEOJxV8EDKame6/aRnNIKcY=", "owner": "nix-community", "repo": "plasma-manager", - "rev": "a4b33606111c9c5dcd10009042bb710307174f51", + "rev": "f3177b3c69fb3f03201098d7fe8ab6422cce7fc1", "type": "github" }, "original": { @@ -1158,11 +1156,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1774221325, - "narHash": "sha256-aEIdkqB8gtQZtEbogdUb5iyfcZpKIlD3FkG8ANu73/I=", + "lastModified": 1775228522, + "narHash": "sha256-+6eTD6EAabjow5gdjWRP6aI2UUwOZJEjzzsvvbVu8f8=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "b42b63f390a4dab14e6efa34a70e67f5b087cc62", + "rev": "f4b77dc99d9925667246e2887783b79bdc46a50d", "type": "github" }, "original": { @@ -1202,11 +1200,11 @@ ] }, "locked": { - "lastModified": 1774154798, - "narHash": "sha256-zsTuloDSdKf+PrI1MsWx5z/cyGEJ8P3eERtAfdP8Bmg=", + "lastModified": 1775365543, + "narHash": "sha256-f50qrK0WwZ9z5EdaMGWOTtALgSF7yb7XwuE7LjCuDmw=", "owner": "Mic92", "repo": "sops-nix", - "rev": "3e0d543e6ba6c0c48117a81614e90c6d8c425170", + "rev": "a4ee2de76efb759fe8d4868c33dec9937897916f", "type": "github" }, "original": { @@ -1220,11 +1218,11 @@ "nixpkgs": "nixpkgs_10" }, "locked": { - "lastModified": 1774154798, - "narHash": "sha256-zsTuloDSdKf+PrI1MsWx5z/cyGEJ8P3eERtAfdP8Bmg=", + "lastModified": 1775365543, + "narHash": "sha256-f50qrK0WwZ9z5EdaMGWOTtALgSF7yb7XwuE7LjCuDmw=", "owner": "Mic92", "repo": "sops-nix", - "rev": "3e0d543e6ba6c0c48117a81614e90c6d8c425170", + "rev": "a4ee2de76efb759fe8d4868c33dec9937897916f", "type": "github" }, "original": { @@ -1245,18 +1243,17 @@ "nixpkgs": "nixpkgs_11", "nur": "nur", "systems": "systems_6", - "tinted-foot": "tinted-foot", "tinted-kitty": "tinted-kitty", "tinted-schemes": "tinted-schemes", "tinted-tmux": "tinted-tmux", "tinted-zed": "tinted-zed" }, "locked": { - "lastModified": 1774124764, - "narHash": "sha256-Poz9WTjiRlqZIf197CrMMJfTifZhrZpbHFv0eU1Nhtg=", + "lastModified": 1775247334, + "narHash": "sha256-eVKt8wpQqg6Hq/UdHQkV1izXGloGQxdlE4SSk9/X27s=", "owner": "nix-community", "repo": "stylix", - "rev": "e31c79f571c5595a155f84b9d77ce53a84745494", + "rev": "6d0502ef7447090abf8b00362b5cda8ac64595b4", "type": "github" }, "original": { @@ -1392,23 +1389,6 @@ "type": "github" } }, - "tinted-foot": { - "flake": false, - "locked": { - "lastModified": 1726913040, - "narHash": "sha256-+eDZPkw7efMNUf3/Pv0EmsidqdwNJ1TaOum6k7lngDQ=", - "owner": "tinted-theming", - "repo": "tinted-foot", - "rev": "fd1b924b6c45c3e4465e8a849e67ea82933fcbe4", - "type": "github" - }, - "original": { - "owner": "tinted-theming", - "repo": "tinted-foot", - "rev": "fd1b924b6c45c3e4465e8a849e67ea82933fcbe4", - "type": "github" - } - }, "tinted-kitty": { "flake": false, "locked": { @@ -1428,11 +1408,11 @@ "tinted-schemes": { "flake": false, "locked": { - "lastModified": 1767710407, - "narHash": "sha256-+W1EB79Jl0/gm4JqmO0Nuc5C7hRdp4vfsV/VdzI+des=", + "lastModified": 1772661346, + "narHash": "sha256-4eu3LqB9tPqe0Vaqxd4wkZiBbthLbpb7llcoE/p5HT0=", "owner": "tinted-theming", "repo": "schemes", - "rev": "2800e2b8ac90f678d7e4acebe4fa253f602e05b2", + "rev": "13b5b0c299982bb361039601e2d72587d6846294", "type": "github" }, "original": { @@ -1444,11 +1424,11 @@ "tinted-tmux": { "flake": false, "locked": { - "lastModified": 1767489635, - "narHash": "sha256-e6nnFnWXKBCJjCv4QG4bbcouJ6y3yeT70V9MofL32lU=", + "lastModified": 1772934010, + "narHash": "sha256-x+6+4UvaG+RBRQ6UaX+o6DjEg28u4eqhVRM9kpgJGjQ=", "owner": "tinted-theming", "repo": "tinted-tmux", - "rev": "3c32729ccae99be44fe8a125d20be06f8d7d8184", + "rev": "c3529673a5ab6e1b6830f618c45d9ce1bcdd829d", "type": "github" }, "original": { @@ -1460,11 +1440,11 @@ "tinted-zed": { "flake": false, "locked": { - "lastModified": 1767488740, - "narHash": "sha256-wVOj0qyil8m+ouSsVZcNjl5ZR+1GdOOAooAatQXHbuU=", + "lastModified": 1772909925, + "narHash": "sha256-jx/5+pgYR0noHa3hk2esin18VMbnPSvWPL5bBjfTIAU=", "owner": "tinted-theming", "repo": "base16-zed", - "rev": "11abb0b282ad3786a2aae088d3a01c60916f2e40", + "rev": "b4d3a1b3bcbd090937ef609a0a3b37237af974df", "type": "github" }, "original": { @@ -1481,11 +1461,11 @@ ] }, "locked": { - "lastModified": 1773297127, - "narHash": "sha256-6E/yhXP7Oy/NbXtf1ktzmU8SdVqJQ09HC/48ebEGBpk=", + "lastModified": 1775125835, + "narHash": "sha256-2qYcPgzFhnQWchHo0SlqLHrXpux5i6ay6UHA+v2iH4U=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "71b125cd05fbfd78cab3e070b73544abe24c5016", + "rev": "75925962939880974e3ab417879daffcba36c4a3", "type": "github" }, "original": { @@ -1502,11 +1482,11 @@ ] }, "locked": { - "lastModified": 1774242250, - "narHash": "sha256-pchbnY7KVnH26g4O3LZO8vpshInqNj937gAqlPob1Mk=", + "lastModified": 1775367672, + "narHash": "sha256-nGC6qrRsWysfR7/8wsSooq0X71rfJjhq1b+dFI6oQtY=", "owner": "0xc000022070", "repo": "zen-browser-flake", - "rev": "f19c3e6683c2d2f3fcfcb88fb691931a104bc47c", + "rev": "33cd729244914f1e121477c5de148639c5e73c4a", "type": "github" }, "original": { From 7b37c0e9c3f0851322189763be6f47a4655d3075 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Sun, 5 Apr 2026 16:05:01 +0200 Subject: [PATCH 091/108] various fixes --- modules/nixos/services/media/servarr/default.nix | 1 + .../nixos/services/networking/caddy/default.nix | 14 ++++---------- .../services/security/vaultwarden/default.nix | 2 +- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/modules/nixos/services/media/servarr/default.nix b/modules/nixos/services/media/servarr/default.nix index c7a066c..a98399d 100644 --- a/modules/nixos/services/media/servarr/default.nix +++ b/modules/nixos/services/media/servarr/default.nix @@ -103,6 +103,7 @@ in { port = 2009; host_whitelist = "${config.networking.hostName}"; + permissions = "770"; download_dir = "/var/media/downloads/incomplete"; complete_dir = "/var/media/downloads/done"; }; diff --git a/modules/nixos/services/networking/caddy/default.nix b/modules/nixos/services/networking/caddy/default.nix index ec9df3a..e18a707 100644 --- a/modules/nixos/services/networking/caddy/default.nix +++ b/modules/nixos/services/networking/caddy/default.nix @@ -10,15 +10,6 @@ cfg = config.${namespace}.services.networking.caddy; hasHosts = (cfg.hosts |> attrNames |> length) > 0; - caddyBase = pkgs.callPackage "${pkgs.path}/pkgs/by-name/ca/caddy/package.nix" { - buildGo125Module = pkgs.buildGo126Module; - caddy = caddyBase; - }; - caddyPackage = - caddyBase.withPlugins { - plugins = ["github.com/corazawaf/coraza-caddy/v2@v2.1.0"]; - hash = "sha256-pSXjLaZoRtKV3eFl2ySRSjl3yxi514G1Cb7pfrpxxtE="; - }; in { options.${namespace}.services.networking.caddy = { enable = mkEnableOption "enable caddy" // {default = true;}; @@ -36,7 +27,10 @@ in { services.caddy = { enable = cfg.enable; - package = caddyPackage; + package = pkgs.caddy.withPlugins { + plugins = ["github.com/corazawaf/coraza-caddy/v2@v2.1.0"]; + hash = "sha256-pSXjLaZoRtKV3eFl2ySRSjl3yxi514G1Cb7pfrpxxtE="; + }; virtualHosts = cfg.hosts diff --git a/modules/nixos/services/security/vaultwarden/default.nix b/modules/nixos/services/security/vaultwarden/default.nix index 7dce380..089c945 100644 --- a/modules/nixos/services/security/vaultwarden/default.nix +++ b/modules/nixos/services/security/vaultwarden/default.nix @@ -118,7 +118,7 @@ in { enable = true; dbBackend = "postgresql"; - package = pkgs.${namespace}.vaultwarden; + package = pkgs.vaultwarden-postgresql; config = { SIGNUPS_ALLOWED = false; From 352569fd8bd25a29b008972d7d30b05e898010c5 Mon Sep 17 00:00:00 2001 From: chris Date: Sun, 12 Apr 2026 12:03:43 +0000 Subject: [PATCH 092/108] chore(secrets): set secret "backup/ssh-key" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 005042c..7d44c82 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -40,6 +40,8 @@ coturn: qbittorrent: password: ENC[AES256_GCM,data:LIDxh0Ni0JgQGWFix/Ihw7IlUPgzMhrMlWNP5LKkAnEM6EoqA9kFwiPeizB0CZ20+vSqRiL9fikBf8qGLA17L7AKh8I4OTFDlpKpMRtRlMq9S5UBEyOqtOMcvkCSf6/qGoORd1KJSlaitZk47SYRuccOpy/2vAvbMRdLm0SYEqc=,iv:tQdN1N9kXoq7OZbR2eYyy50FltsMAAUI4Lr7U4/SpJE=,tag:3ZOLvjHXD7i7WFy1/Ggqtg==,type:str] password_hash: ENC[AES256_GCM,data:urufJbSErLqPdU6jLLZk+27fe4k+cKLXcGRGSqroUDdGMzDnhSF+ZWuPxwDlJQR3ws2GnuiEASncwNO/SALKXFDk2V2gsKJ4hsjyiIbsqCwSEFB/XMY0nY/x0xrcIfMVE0HdrNYeQ3zT01Z5jQpSd7wo2M63LaULL/Av498=,iv:tnUVhOgrImKa6iii2hJZn5LKrySM5v47B2zDZMgmUow=,tag:g3xa/4Z+t1Q9Wnd4XzefLg==,type:str] +backup: + ssh-key: ENC[AES256_GCM,data:aRY+9mYssEXPmfJQ2KOYU4wxkgzgYbv3GJ4KUkECSZ6IdQVv4CpKMg75dEhO5/t7MYjiNXze5WibZ0UHSTnUv4OB6NP6Mp1HZjIZb6paCJxjkoul0BVwtF5AKViJe0LIKoh+,iv:kZgZTqgYdqJSD6rO3lj/IFqhO9mYgZ7YYOCS2b+xpXQ=,tag:xPh0yL2uMyqgrioC36PPpA==,type:str] sops: age: - recipient: age19qfpf980tadguqq44zf6xwvjvl428dyrj46ha3n6aeqddwhtnuqqml7etq @@ -60,7 +62,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-03-05T10:38:19Z" - mac: ENC[AES256_GCM,data:gS6YTRTl6UdOC7Afrj1LrkgA7MWRLF0HNWytfzhkvThLW+JJrHPEhvWiYrsPW1Bm6o2JkKqVP5HfzcuGNIHJySkEQ4HV02BbibtMNiUKqk+voATsWOpo6957bwRJaTbvDvxmzIQ38TSUoj/pt8Z8WTl0hSPAlqNlWYffXX0y8K4=,iv:53R2bKYKiHJi9DTecg7hiuGNb3Kj9rA2U/oPJ+AFO5I=,tag:5uqvmEJCaCS/yNqyt/FPZg==,type:str] + lastmodified: "2026-04-12T12:03:36Z" + mac: ENC[AES256_GCM,data:pt6G4PVEygk7LV2qwQY1s9CSDUJ4CM3/Jo6Y16jdYb2LDf7YR4INHBk9+p4rK9kMKda6jRlFXtqcE7exIJLzzLCZD22EUZE7P/GKjYhHKu+ros9NaDBLHcdzxMhDazu+CUUITS0yp1lzCEihC4PxY2Z+uv5N0m42VS2bsem7GKg=,iv:6VD8o/t/XQ6yI0DI6KwdR42q0hGOvPVQ6uADNy5lakk=,tag:3bsnxSNU1mLU0UcyZzKhVw==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From a1d4c244cf9e9ab0304c28657430b5926e7b757c Mon Sep 17 00:00:00 2001 From: chris Date: Sun, 12 Apr 2026 15:00:09 +0000 Subject: [PATCH 093/108] chore(secrets): set secret "zitadel/users" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 7d44c82..43c2b4c 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -4,7 +4,7 @@ email: zitadel: masterKey: ENC[AES256_GCM,data:4MPvBo407qrS7NF4oUTf84tZoPkSRmiHdD7qpkYeHME=,iv:H2NIAN0xBUDqnyco9gA3zYAsKtSeA/JpqYrPhc1eqc0=,tag:6OFGDfsucG5gDerImgpuXA==,type:str] nix: {} - users: ENC[AES256_GCM,data:w/2Vdq0EHXaJ5u/aA/reSCtwRHreWm1U1WoJT927xV81zoN0ytoYOwush610caZu8vVXkL4b0hysK77dyWJkdkYpwLY8xG9pLkYlU3lN5E/2tgEjB7Dd7oY7TFTCNuypmIzYh6V74KiHMeA0vlyWUp9lLNt40Ro3MZLT42DyTYjF6YBoUHUp0fS0rKypILJGobJBrwz2YWagXj80IqaaUmmsIcYAaM2u3dQviLlRkIyUxPd1wjFoMc/OMp5Y8A4ZHroCN0wJitGeEEP33GD+MUy58u05pA430AD5Mo4H2V7b3t0qIkOQ8a0BgSVA8UqmrcY/TfikuIZ1kTyCxvD7kmjPq5tG+bhtHt85wgk1XffVO3NDTK7UrltO8R6KolQ5bBgcKgl7YnFTN5qSAT+xrYg8oZaPrGQBTx6eEVETKHKe4oSDkGlAle86lenhF+jm3k2ALmH9X3P/TpAtfRhuU+sUKqhrqQ2Nf4M7LfBtd7lyt2ESqilKokcl51gWCY+1B75dCEIdb/BPmpwzJBGFOI2nZqhxFnVa8TyMpT7C2TxK7rCBPDt5NnNvWYc4+8sRXHBz7s2R5NTk4gaJODlo3HvyL0MV,iv:XlO48HKJWRgwsozmgXstfirwb5CUY+ywelbgLlcx/n4=,tag:GuQMkL2mpNkTJIep79x0zw==,type:str] + users: ENC[AES256_GCM,data:ikpAuiQT32i4+aaVPz/nRqlf5ESID3khat2MrOySOfF9duJaQLWBonaKau6JVRljnGb+RGTiEH/EpxzXHnNydfHrir/jS4cDFMUMNV9aee0CyEbfqHAFqbC3B4ReZZE+XCkiq1j5jLnRg7EiGRK5+g+ul2iGIAwJ5SoHiOSSBcJ2E4B+AdkhGVO6Qsf+DW3hUZ/MsoaDsOB3IX15iC6/9z+NT+/Jefz5In6jn/vdYpD2i/zWvNHHPVXIkK1Co8FUidRdOjyWiiCb4+A0DI5v9E69xKe4zl26GHv3+1aK7cTxq2meDI4AXKhaTpak0A/neO/E6Xrc78752rTNRUDre9jJNrip/UPu8KvaCzpUi8Y4aN2Qg6ICF6JudzgouFyOGJ/JyxjVcJhUBOof/vCOcihdmHlo8sgyAi5mn/70VqnEF6Ei4KkRMAMlz9mfEVHDmjWMP1wHLw8eJD+Vhn/AJ76VecSCr51OHYtwgEcQXC6ikyPwBn8XQ5CNae/XGhcs0c8UbAcUXCH40zxvn4DFYHzJCkwurqv2iiV5zRN+rre6SoEWIToByq5KAwzkgLrLIVIbYWcLXlBYLvuMjnHbRknqWndQS72fRds0EWg+/OfjO+0SrPkJIoHkMNiUUmoq17ouwz0mcKVEh3o1Wptrp54ArDLkUjdtbOhaGTEzpGH+y0b+LITiN0erGPFITjf8sgGtvg+fRnoqCxPpex99,iv:+MjTW26sd8csWm4RXscFMgUm3wNY5Yj+qP8Xfg/WvsQ=,tag:mXjrEJqpbuqaVLa8EJpjoQ==,type:str] forgejo: action_runner_token: ENC[AES256_GCM,data:yJ6OnRq5kinbuhvH06K5o3l86EafuBoojMwg/qhP+cgeH+BwPeE+Ng==,iv:IeXJahPxgLNIUFmkgp495tLVh8UyQBmJ2SnVEUhlhHs=,tag:XYQi613CxSp8AQeilJMrsg==,type:str] synapse: @@ -62,7 +62,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-04-12T12:03:36Z" - mac: ENC[AES256_GCM,data:pt6G4PVEygk7LV2qwQY1s9CSDUJ4CM3/Jo6Y16jdYb2LDf7YR4INHBk9+p4rK9kMKda6jRlFXtqcE7exIJLzzLCZD22EUZE7P/GKjYhHKu+ros9NaDBLHcdzxMhDazu+CUUITS0yp1lzCEihC4PxY2Z+uv5N0m42VS2bsem7GKg=,iv:6VD8o/t/XQ6yI0DI6KwdR42q0hGOvPVQ6uADNy5lakk=,tag:3bsnxSNU1mLU0UcyZzKhVw==,type:str] + lastmodified: "2026-04-12T15:00:06Z" + mac: ENC[AES256_GCM,data:oklhIZY2AHJh/RaY58R4JZzd8l+aSqxco0qNEhHKskuxB6TPHsybJy93J0oFP/VkuOheuMG4Z32WBAL9dSntjKoWCFdlUf9IMXPUYXy+yD2J0/Lf6w7hXNPQFlDrPfZ+2klamJDZDpkY5SAcgLFHG8oZVLsJtCj6uH+dQKG9QXI=,iv:ZKnwGjqy/to0auzUZnU7bCARZg54hqskr+FOXwxS/dY=,tag:NVkqznP3Qcsyui/EAD9QJA==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From 03bd906aef981b0590d27389638e8125d30626c1 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Sun, 12 Apr 2026 17:53:06 +0200 Subject: [PATCH 094/108] fix vaultwarden oidc --- modules/nixos/services/security/vaultwarden/default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/nixos/services/security/vaultwarden/default.nix b/modules/nixos/services/security/vaultwarden/default.nix index 089c945..1660736 100644 --- a/modules/nixos/services/security/vaultwarden/default.nix +++ b/modules/nixos/services/security/vaultwarden/default.nix @@ -135,7 +135,7 @@ in { SSO_ROLES_ENABLED = true; SSO_ORGANIZATIONS_ENABLED = true; SSO_ORGANIZATIONS_REVOCATION = true; - SSO_AUTHORITY = "https://auth.kruining.eu/"; + SSO_AUTHORITY = "https://auth.kruining.eu"; SSO_SCOPES = "email profile offline_access"; ROCKET_ADDRESS = "::1"; From 66fc9e532a5d68d0c4f7cde8ebea1fac45dddfa1 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Sun, 12 Apr 2026 17:53:37 +0200 Subject: [PATCH 095/108] add backup stuff --- systems/x86_64-linux/ulmo/default.nix | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/systems/x86_64-linux/ulmo/default.nix b/systems/x86_64-linux/ulmo/default.nix index 43a5760..7c20a11 100644 --- a/systems/x86_64-linux/ulmo/default.nix +++ b/systems/x86_64-linux/ulmo/default.nix @@ -1,9 +1,21 @@ -{...}: { +{ + pkgs, + config, + ... +}: { imports = [ ./disks.nix ./hardware.nix ]; + environment.systemPackages = with pkgs; [bup]; + services.postgresqlBackup = { + enable = true; + backupAll = true; + startAt = "*-*-* 01:00:00"; + location = "/var/backup/postgresql"; + }; + networking = { interfaces.enp2s0 = { ipv6.addresses = [ From d5b5166b95d0e00f693c8189948c0b63e039fc09 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Tue, 14 Apr 2026 15:27:49 +0200 Subject: [PATCH 096/108] checkpoint --- .../services/communication/matrix/default.nix | 28 +++++++++++++++++-- .../nixos/services/media/servarr/default.nix | 9 +++--- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/modules/nixos/services/communication/matrix/default.nix b/modules/nixos/services/communication/matrix/default.nix index d2e47b0..903de32 100644 --- a/modules/nixos/services/communication/matrix/default.nix +++ b/modules/nixos/services/communication/matrix/default.nix @@ -17,6 +17,10 @@ database = "synapse"; keyFile = "/var/lib/element-call/key"; in { + imports = [ + ./mautrix-starr.nix + ]; + options.${namespace}.services.communication.matrix = { enable = mkEnableOption "Matrix server (Synapse)"; }; @@ -24,7 +28,6 @@ in { config = mkIf cfg.enable { ${namespace}.services = { persistance.postgresql.enable = true; - # virtualisation.podman.enable = true; networking.caddy = { # globalConfig = '' @@ -255,8 +258,29 @@ in { }; }; + # mautrix-starr = { + # enable = true; + # registerToSynapse = true; + + # settings = { + # appservice = { + # provisioning.enabled = false; + # }; + + # homeserver = { + # address = "http://[::1]:${toString port}"; + # domain = domain; + # }; + + # bridge = { + # permissions = { + # "@chris:${domain}" = "admin"; + # }; + # }; + # }; + # }; + postgresql = { - enable = true; ensureDatabases = [database]; ensureUsers = [ { diff --git a/modules/nixos/services/media/servarr/default.nix b/modules/nixos/services/media/servarr/default.nix index a98399d..ae0e3b0 100644 --- a/modules/nixos/services/media/servarr/default.nix +++ b/modules/nixos/services/media/servarr/default.nix @@ -129,11 +129,12 @@ in { port = 2007; }; - postgresql = { - ensureDatabases = cfg |> lib.attrNames; + postgresql = let + databases = [] ++ (cfg |> lib.attrNames); + in { + ensureDatabases = databases; ensureUsers = - cfg - |> lib.attrNames + databases |> lib.map (service: { name = service; ensureDBOwnership = true; From 6fe9387626dd4acee177d415623a31721583fdda Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 16 Apr 2026 05:19:04 +0000 Subject: [PATCH 097/108] chore(secrets): set secret "synapse/shared_secret" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 43c2b4c..8e000ba 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -10,6 +10,7 @@ forgejo: synapse: oidc_id: ENC[AES256_GCM,data:XbCpyGq0LeRJWq8dv/5Dipvp,iv:YDhgl26z1NBbIQLoLdGVz0+ze6o1ZcmgVHPfwoRj57I=,tag:y2vUuqnDmtTvVQmZCAlnLg==,type:str] oidc_secret: ENC[AES256_GCM,data:nVFi5EFbNMZ0mvrDHVYC0NiwJlo2eEw44D+Fcv9SKSb2oO00lGEDkP/oXDj5YgDq6RLQSe3f/SUOn77ntwnZYg==,iv:awe7VNUYOn9ofl1QlQTrEN5d0i5WkVM35qndruL4VXo=,tag:8Yoc9lFF9aWbtAa5fzQGEA==,type:str] + shared_secret: "" radarr: apikey: ENC[AES256_GCM,data:G141GW4PyS5pbAV39HcVscMw3s30txOgTZzWaL7o+ccZfnfDLv796O6xKXdqGZ8saLsveghLw9Z6a5luusHyQ3Q5ESL6W7SVeZVTuSqSC3i/4jl75FJxhnsgVsfrnYxzLGpKiw==,iv:sZl/XLh6y3WgSAn6nH3sFB6atBifZdghm+QsCNDbcjY=,tag:Tw+R80nrF0T0yDti0Uf+ig==,type:str] sonarr: @@ -62,7 +63,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-04-12T15:00:06Z" - mac: ENC[AES256_GCM,data:oklhIZY2AHJh/RaY58R4JZzd8l+aSqxco0qNEhHKskuxB6TPHsybJy93J0oFP/VkuOheuMG4Z32WBAL9dSntjKoWCFdlUf9IMXPUYXy+yD2J0/Lf6w7hXNPQFlDrPfZ+2klamJDZDpkY5SAcgLFHG8oZVLsJtCj6uH+dQKG9QXI=,iv:ZKnwGjqy/to0auzUZnU7bCARZg54hqskr+FOXwxS/dY=,tag:NVkqznP3Qcsyui/EAD9QJA==,type:str] + lastmodified: "2026-04-16T05:19:02Z" + mac: ENC[AES256_GCM,data:UqsihBm/UQu0GcikdvhsMJqt3x3AIoRAA1td5Gi243IPoVvXTdngDlJ4/zXQ1VvJ8JAOcD/i5aE/POi3Ig/oHrzq4MO501JMbRBWShsyQI0YeFAFHCE63S81B7lcQhusq1LEjEODNq0H7c6X1w7LsMJap9gPtS75CaEI4hHN1NY=,iv:IWIx9Fq/KQk/OOTBfEIWRRd4nV9pQJ47ldq+wIwPxtA=,tag:OHlyO31xcqL/bpDMsRMiIA==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From c4e9485ccbec99dad6b140e8f390843cccf4ee34 Mon Sep 17 00:00:00 2001 From: chris Date: Thu, 16 Apr 2026 05:20:19 +0000 Subject: [PATCH 098/108] chore(secrets): set secret "synapse/shared_secret" for machine "ulmo" --- systems/x86_64-linux/ulmo/secrets.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/systems/x86_64-linux/ulmo/secrets.yml b/systems/x86_64-linux/ulmo/secrets.yml index 8e000ba..869e63e 100644 --- a/systems/x86_64-linux/ulmo/secrets.yml +++ b/systems/x86_64-linux/ulmo/secrets.yml @@ -10,7 +10,7 @@ forgejo: synapse: oidc_id: ENC[AES256_GCM,data:XbCpyGq0LeRJWq8dv/5Dipvp,iv:YDhgl26z1NBbIQLoLdGVz0+ze6o1ZcmgVHPfwoRj57I=,tag:y2vUuqnDmtTvVQmZCAlnLg==,type:str] oidc_secret: ENC[AES256_GCM,data:nVFi5EFbNMZ0mvrDHVYC0NiwJlo2eEw44D+Fcv9SKSb2oO00lGEDkP/oXDj5YgDq6RLQSe3f/SUOn77ntwnZYg==,iv:awe7VNUYOn9ofl1QlQTrEN5d0i5WkVM35qndruL4VXo=,tag:8Yoc9lFF9aWbtAa5fzQGEA==,type:str] - shared_secret: "" + shared_secret: ENC[AES256_GCM,data:IkzZ6QV1gLzChAFSsYsK3HM5dKFD4AoDJ53xgoxNpgt5tb45mMw/LRxu4NArGVLUtVGBy6jk6arU+Nxvi8bxPOC8c2UFCRUF+FM1phICEbb4Chgy5g803VKNFOu6BLaEmwDmuZSQP7CwX1hy8TX8yChboHGp7hH+n5SAZpejrLg=,iv:d+Ab91yCltYwudDWhrWPw0Xod/TKriCsoGD8i6PD4H4=,tag:xOXnzNuajcOz+imjMJr3Dg==,type:str] radarr: apikey: ENC[AES256_GCM,data:G141GW4PyS5pbAV39HcVscMw3s30txOgTZzWaL7o+ccZfnfDLv796O6xKXdqGZ8saLsveghLw9Z6a5luusHyQ3Q5ESL6W7SVeZVTuSqSC3i/4jl75FJxhnsgVsfrnYxzLGpKiw==,iv:sZl/XLh6y3WgSAn6nH3sFB6atBifZdghm+QsCNDbcjY=,tag:Tw+R80nrF0T0yDti0Uf+ig==,type:str] sonarr: @@ -63,7 +63,7 @@ sops: TTRWaHhpNWlkVDFmMFN4ZTNHMUxyNVkKV693pzTKRkZboQCMPr9IyMGSgxfuHXcb Y6BNcp6Qg6PWtX5QI7wRkPNINAK1TEbRBba+b8h6gMmVU4DliQyFiQ== -----END AGE ENCRYPTED FILE----- - lastmodified: "2026-04-16T05:19:02Z" - mac: ENC[AES256_GCM,data:UqsihBm/UQu0GcikdvhsMJqt3x3AIoRAA1td5Gi243IPoVvXTdngDlJ4/zXQ1VvJ8JAOcD/i5aE/POi3Ig/oHrzq4MO501JMbRBWShsyQI0YeFAFHCE63S81B7lcQhusq1LEjEODNq0H7c6X1w7LsMJap9gPtS75CaEI4hHN1NY=,iv:IWIx9Fq/KQk/OOTBfEIWRRd4nV9pQJ47ldq+wIwPxtA=,tag:OHlyO31xcqL/bpDMsRMiIA==,type:str] + lastmodified: "2026-04-16T05:20:18Z" + mac: ENC[AES256_GCM,data:YqkxwV30uqSHhsn4niFEODxxl9R2ZuiyyX4g8zONVjMvdA52C08zPpxdxjtXnUT9m3sT7iSmWcJJZwhMhRIb8LJ2sdIJ4v+wpG9I4pPokhEXI2ozqbzw3k68GnZOzYu3kePQBJjQx1fmlM63dgILIwx7ytPnpm9arQ1rszZynNs=,iv:hxdhU5oH9h9mRH3m76oFkYVNA68PnivVJpJRjxSRtTw=,tag:Fyyg6cWPb96c/Vap+PifUQ==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 From ce44496a48452cf69557509b47db445224591fb9 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Thu, 16 Apr 2026 07:46:45 +0200 Subject: [PATCH 099/108] Add arrtrix Matrix bridge service and package scaffolding --- .just/vars.just | 4 +- logs/bridge-2026-04-15T09-11-43.612.log | 2 + logs/bridge.log | 2 + .../services/communication/matrix/default.nix | 432 ++++++++---------- .../nixos/temp/services/arrtrix/default.nix | 200 ++++++++ packages/arrtrix/cmd/arrtrix/main.go | 26 ++ packages/arrtrix/default.nix | 33 ++ packages/arrtrix/go.mod | 43 ++ packages/arrtrix/go.sum | 91 ++++ packages/arrtrix/pkg/connector/config.go | 18 + packages/arrtrix/pkg/connector/connector.go | 107 +++++ .../arrtrix/pkg/connector/example-config.yaml | 7 + script/synapse/shared_secret | 3 + 13 files changed, 712 insertions(+), 256 deletions(-) create mode 100644 logs/bridge-2026-04-15T09-11-43.612.log create mode 100644 logs/bridge.log create mode 100644 modules/nixos/temp/services/arrtrix/default.nix create mode 100644 packages/arrtrix/cmd/arrtrix/main.go create mode 100644 packages/arrtrix/default.nix create mode 100644 packages/arrtrix/go.mod create mode 100644 packages/arrtrix/go.sum create mode 100644 packages/arrtrix/pkg/connector/config.go create mode 100644 packages/arrtrix/pkg/connector/connector.go create mode 100644 packages/arrtrix/pkg/connector/example-config.yaml create mode 100644 script/synapse/shared_secret diff --git a/.just/vars.just b/.just/vars.just index 62a8bd9..2ae9a44 100644 --- a/.just/vars.just +++ b/.just/vars.just @@ -43,14 +43,14 @@ generate machine: # Skip if we already have a value [ $(just vars get "{{ machine }}" "$key" | jq -r) ] && continue - just _rotate "{{ machine }}" "$key" + just vars _rotate "{{ machine }}" "$key" done [doc('Regenerate var values for {machine}')] [script] _rotate machine key: # Exit if there's no script - [ -f "{{ justfile_directory() }}/script/{{ key }}" ] || exit + [ -f "{{ justfile_directory() }}/script/{{ key }}" ] || exit 0 echo "Executing script for {{ key }}" just vars set "{{ machine }}" "{{ key }}" "$(cd -- "$(dirname "{{ justfile_directory() }}/script/{{ key }}")" && source "./$(basename "{{ key }}")")" diff --git a/logs/bridge-2026-04-15T09-11-43.612.log b/logs/bridge-2026-04-15T09-11-43.612.log new file mode 100644 index 0000000..df81d78 --- /dev/null +++ b/logs/bridge-2026-04-15T09-11-43.612.log @@ -0,0 +1,2 @@ +{"level":"fatal","error":"homeserver.address not configured","time":"2026-04-15T09:10:06.949460064Z","message":"Configuration error"} +{"level":"info","time":"2026-04-15T09:10:06.949840013Z","message":"See https://docs.mau.fi/faq/field-unconfigured for more info"} diff --git a/logs/bridge.log b/logs/bridge.log new file mode 100644 index 0000000..63567e0 --- /dev/null +++ b/logs/bridge.log @@ -0,0 +1,2 @@ +{"level":"fatal","error":"appservice.as_token not configured. Did you forget to generate the registration? ","time":"2026-04-15T09:11:43.617908298Z","message":"Configuration error"} +{"level":"info","time":"2026-04-15T09:11:43.618232253Z","message":"See https://docs.mau.fi/faq/field-unconfigured for more info"} diff --git a/modules/nixos/services/communication/matrix/default.nix b/modules/nixos/services/communication/matrix/default.nix index 903de32..c9c11f1 100644 --- a/modules/nixos/services/communication/matrix/default.nix +++ b/modules/nixos/services/communication/matrix/default.nix @@ -6,7 +6,7 @@ ... }: let inherit (builtins) toString toJSON; - inherit (lib) mkIf mkEnableOption; + inherit (lib) mkIf mkEnableOption mkMerge; cfg = config.${namespace}.services.communication.matrix; @@ -16,11 +16,36 @@ database = "synapse"; keyFile = "/var/lib/element-call/key"; -in { - imports = [ - ./mautrix-starr.nix - ]; + mkMautrix = bridge: i: conf: { + ${bridge} = + { + enable = true; + registerToSynapse = true; + + settings = { + appservice = { + # hostname = "[::]"; + # port = 40010 + i; + # address = "http://${config.services.${bridge}.settings.appservice.hostname}:${toString config.services.${bridge}.settings.appservice.port}"; + provisioning.enabled = false; + }; + + homeserver = { + inherit domain; + address = "http://[::1]:${toString port}"; + }; + + bridge = { + permissions = { + "@chris:${domain}" = "admin"; + }; + }; + }; + } + // conf; + }; +in { options.${namespace}.services.communication.matrix = { enable = mkEnableOption "Matrix server (Synapse)"; }; @@ -30,24 +55,6 @@ in { persistance.postgresql.enable = true; networking.caddy = { - # globalConfig = '' - # layer4 { - # 127.0.0.1:4004 - # route { - # proxy { - # upstream synapse:4004 - # } - # } - # } - # 127.0.0.1:4005 - # route { - # proxy { - # upstream synapse:4005 - # } - # } - # } - # } - # ''; hosts = let server = { "m.server" = "${fqn}:443"; @@ -99,259 +106,166 @@ in { }; }; - services = { - matrix-synapse = { - enable = true; + services = mkMerge [ + (mkMautrix "mautrix-signal" 1 {}) + (mkMautrix "mautrix-telegram" 2 {}) + (mkMautrix "mautrix-whatsapp" 3 {}) + (mkMautrix "arrtrix" 4 {}) + { + matrix-synapse = { + enable = true; - extras = ["oidc"]; + extras = ["oidc"]; - extraConfigFiles = [ - config.sops.templates."synapse-oidc.yaml".path - ]; + extraConfigFiles = [ + config.sops.templates."synapse.yaml".path + config.sops.templates."synapse-oidc.yaml".path + ]; - settings = { - server_name = domain; - public_baseurl = "https://${fqn}"; + settings = { + server_name = domain; + public_baseurl = "https://${fqn}"; - enable_metrics = true; + enable_metrics = true; - registration_shared_secret = "tZtBnlhEmLbMwF0lQ112VH1Rl5MkZzYH9suI4pEoPXzk6nWUB8FJF4eEnwLkbstz"; + url_preview_enabled = true; + precence.enabled = true; - url_preview_enabled = true; - precence.enabled = true; + # Since we'll be using OIDC for auth disable all local options + enable_registration = false; + enable_registration_without_verification = false; + password_config.enabled = true; + backchannel_logout_enabled = true; - # Since we'll be using OIDC for auth disable all local options - enable_registration = false; - enable_registration_without_verification = false; - password_config.enabled = true; - backchannel_logout_enabled = true; - - # Element Call options - max_event_delay_duration = "24h"; - rc_message = { - per_second = 0.5; - burst_count = 30; - }; - rc_delayed_event_mgmt = { - per_second = 1; - burst_count = 20; - }; - turn_uris = ["turn:turn.${domain}:4004?transport=udp" "turn:turn.${domain}:4004?transport=tcp"]; - - experimental_features = { - # MSC2965: OAuth 2.0 Authorization Server Metadata discovery - msc2965_enabled = true; - - # MSC3266: Room summary API. Used for knocking over federation - msc3266_enabled = true; - # MSC4222 needed for syncv2 state_after. This allow clients to - # correctly track the state of the room. - msc4222_enabled = true; - }; - - sso = { - client_whitelist = ["http://[::1]:9092/" "https://auth.kruining.eu/"]; - update_profile_information = true; - }; - - database = { - # this is postgresql (also the default, but I prefer to be explicit) - name = "psycopg2"; - args = { - database = database; - user = database; + # Element Call options + max_event_delay_duration = "24h"; + rc_message = { + per_second = 0.5; + burst_count = 30; }; + rc_delayed_event_mgmt = { + per_second = 1; + burst_count = 20; + }; + turn_uris = ["turn:turn.${domain}:4004?transport=udp" "turn:turn.${domain}:4004?transport=tcp"]; + + experimental_features = { + # MSC2965: OAuth 2.0 Authorization Server Metadata discovery + msc2965_enabled = true; + + # MSC3266: Room summary API. Used for knocking over federation + msc3266_enabled = true; + # MSC4222 needed for syncv2 state_after. This allow clients to + # correctly track the state of the room. + msc4222_enabled = true; + }; + + sso = { + client_whitelist = ["http://[::1]:9092/" "https://auth.kruining.eu/"]; + update_profile_information = true; + }; + + database = { + # this is postgresql (also the default, but I prefer to be explicit) + name = "psycopg2"; + args = { + database = database; + user = database; + }; + }; + + listeners = [ + { + bind_addresses = ["::"]; + port = port; + type = "http"; + tls = false; + x_forwarded = true; + + resources = [ + { + names = ["client" "federation" "openid" "metrics" "media" "health"]; + compress = true; + } + ]; + } + ]; }; + }; - listeners = [ + postgresql = { + ensureDatabases = [database]; + ensureUsers = [ { - bind_addresses = ["::"]; - port = port; - type = "http"; - tls = false; - x_forwarded = true; - - resources = [ - { - names = ["client" "federation" "openid" "metrics" "media" "health"]; - compress = true; - } - ]; + name = database; + ensureDBOwnership = true; } ]; }; - }; - mautrix-signal = { - enable = true; - registerToSynapse = true; + livekit = { + enable = true; + openFirewall = true; + inherit keyFile; - settings = { - appservice = { - provisioning.enabled = false; - }; - - homeserver = { - address = "http://[::1]:${toString port}"; - domain = domain; - }; - - bridge = { - permissions = { - "@chris:${domain}" = "admin"; - }; + settings = { + port = 4002; + room.auto_create = false; }; }; - }; - mautrix-telegram = { - enable = true; - registerToSynapse = true; - - settings = { - telegram = { - api_id = 32770816; - api_hash = "7b63778a976619c9d4ab62adc51cde79"; - bot_token = "disabled"; - - catch_up = true; - sequential_updates = true; - }; - - appservice = { - port = 40011; - provisioning.enabled = false; - }; - - homeserver = { - address = "http://[::1]:${toString port}"; - domain = domain; - }; - - bridge = { - permissions = { - "@chris:${domain}" = "admin"; - }; - }; + lk-jwt-service = { + enable = true; + port = 4003; + # can be on the same virtualHost as synapse + livekitUrl = "wss://${domain}/livekit/sfu"; + inherit keyFile; }; - }; - mautrix-whatsapp = { - enable = true; - registerToSynapse = true; - - settings = { - appservice = { - provisioning.enabled = false; - }; - - homeserver = { - address = "http://[::1]:${toString port}"; - domain = domain; - }; - - bridge = { - permissions = { - "@chris:${domain}" = "admin"; - }; - }; + coturn = rec { + enable = true; + listening-port = 4004; + tls-listening-port = 40004; + no-cli = true; + no-tcp-relay = true; + min-port = 50000; + max-port = 50100; + use-auth-secret = true; + static-auth-secret-file = config.sops.secrets."coturn/secret".path; + realm = "turn.${domain}"; + # cert = "${config.security.acme.certs.${realm}.directory}/full.pem"; + # pkey = "${config.security.acme.certs.${realm}.directory}/key.pem"; + extraConfig = '' + # for debugging + verbose + # ban private IP ranges + no-multicast-peers + denied-peer-ip=0.0.0.0-0.255.255.255 + denied-peer-ip=10.0.0.0-10.255.255.255 + denied-peer-ip=100.64.0.0-100.127.255.255 + denied-peer-ip=127.0.0.0-127.255.255.255 + denied-peer-ip=169.254.0.0-169.254.255.255 + denied-peer-ip=172.16.0.0-172.31.255.255 + denied-peer-ip=192.0.0.0-192.0.0.255 + denied-peer-ip=192.0.2.0-192.0.2.255 + denied-peer-ip=192.88.99.0-192.88.99.255 + denied-peer-ip=192.168.0.0-192.168.255.255 + denied-peer-ip=198.18.0.0-198.19.255.255 + denied-peer-ip=198.51.100.0-198.51.100.255 + denied-peer-ip=203.0.113.0-203.0.113.255 + denied-peer-ip=240.0.0.0-255.255.255.255 + denied-peer-ip=::1 + denied-peer-ip=64:ff9b::-64:ff9b::ffff:ffff + denied-peer-ip=::ffff:0.0.0.0-::ffff:255.255.255.255 + denied-peer-ip=100::-100::ffff:ffff:ffff:ffff + denied-peer-ip=2001::-2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff + denied-peer-ip=2002::-2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff + denied-peer-ip=fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff + denied-peer-ip=fe80::-febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff + ''; }; - }; - - # mautrix-starr = { - # enable = true; - # registerToSynapse = true; - - # settings = { - # appservice = { - # provisioning.enabled = false; - # }; - - # homeserver = { - # address = "http://[::1]:${toString port}"; - # domain = domain; - # }; - - # bridge = { - # permissions = { - # "@chris:${domain}" = "admin"; - # }; - # }; - # }; - # }; - - postgresql = { - ensureDatabases = [database]; - ensureUsers = [ - { - name = database; - ensureDBOwnership = true; - } - ]; - }; - - livekit = { - enable = true; - openFirewall = true; - inherit keyFile; - - settings = { - port = 4002; - room.auto_create = false; - }; - }; - - lk-jwt-service = { - enable = true; - port = 4003; - # can be on the same virtualHost as synapse - livekitUrl = "wss://${domain}/livekit/sfu"; - inherit keyFile; - }; - - coturn = rec { - enable = true; - listening-port = 4004; - tls-listening-port = 40004; - no-cli = true; - no-tcp-relay = true; - min-port = 50000; - max-port = 50100; - use-auth-secret = true; - static-auth-secret-file = config.sops.secrets."coturn/secret".path; - realm = "turn.${domain}"; - # cert = "${config.security.acme.certs.${realm}.directory}/full.pem"; - # pkey = "${config.security.acme.certs.${realm}.directory}/key.pem"; - extraConfig = '' - # for debugging - verbose - # ban private IP ranges - no-multicast-peers - denied-peer-ip=0.0.0.0-0.255.255.255 - denied-peer-ip=10.0.0.0-10.255.255.255 - denied-peer-ip=100.64.0.0-100.127.255.255 - denied-peer-ip=127.0.0.0-127.255.255.255 - denied-peer-ip=169.254.0.0-169.254.255.255 - denied-peer-ip=172.16.0.0-172.31.255.255 - denied-peer-ip=192.0.0.0-192.0.0.255 - denied-peer-ip=192.0.2.0-192.0.2.255 - denied-peer-ip=192.88.99.0-192.88.99.255 - denied-peer-ip=192.168.0.0-192.168.255.255 - denied-peer-ip=198.18.0.0-198.19.255.255 - denied-peer-ip=198.51.100.0-198.51.100.255 - denied-peer-ip=203.0.113.0-203.0.113.255 - denied-peer-ip=240.0.0.0-255.255.255.255 - denied-peer-ip=::1 - denied-peer-ip=64:ff9b::-64:ff9b::ffff:ffff - denied-peer-ip=::ffff:0.0.0.0-::ffff:255.255.255.255 - denied-peer-ip=100::-100::ffff:ffff:ffff:ffff - denied-peer-ip=2001::-2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff - denied-peer-ip=2002::-2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff - denied-peer-ip=fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff - denied-peer-ip=fe80::-febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff - ''; - }; - }; + } + ]; networking.firewall = { allowedTCPPortRanges = []; @@ -400,6 +314,9 @@ in { "synapse/oidc_secret" = { restartUnits = ["synapse-matrix.service"]; }; + "synapse/shared_secret" = { + restartUnits = ["synapse-matrix.service"]; + }; "coturn/secret" = { owner = config.systemd.services.coturn.serviceConfig.User; group = config.systemd.services.coturn.serviceConfig.Group; @@ -408,6 +325,13 @@ in { }; templates = { + "synapse.yaml" = { + owner = "matrix-synapse"; + content = '' + registration_shared_secret: ${config.sops.placeholder."synapse/shared_secret"} + ''; + restartUnits = ["matrix-synapse.service"]; + }; "synapse-oidc.yaml" = { owner = "matrix-synapse"; content = '' diff --git a/modules/nixos/temp/services/arrtrix/default.nix b/modules/nixos/temp/services/arrtrix/default.nix new file mode 100644 index 0000000..dfd7b32 --- /dev/null +++ b/modules/nixos/temp/services/arrtrix/default.nix @@ -0,0 +1,200 @@ +{ + config, + lib, + pkgs, + namespace, + ... +}: let + inherit (lib) mkEnableOption mkPackageOption mkIf mkOption optionalAttrs recursiveUpdate types baseNameOf; + + cfg = config.services.arrtrix; + dataDir = "/var/lib/arrtrix"; + registrationFile = "${dataDir}/arrtrix-registration.yaml"; + settingsFile = "${dataDir}/config.yaml"; + settingsFileUnsubstituted = settingsFormat.generate "arrtrix-config-unsubstituted.json" cfg.settings; + settingsFormat = pkgs.formats.json {}; + + defaultConfig = { + bridge = { + command_prefix = "!arr"; + relay.enabled = true; + permissions."*" = "relay"; + }; + database = { + type = "sqlite3"; + uri = "file:${dataDir}/arrtrix.db?_txlock=immediate"; + }; + homeserver = { + address = "http://localhost:8448"; + domain = config.services.matrix-synapse.settings.server_name or "example.com"; + }; + appservice = { + hostname = "[::]"; + port = 29329; + id = "arrtrix"; + bot = { + username = "arrtrixbot"; + displayname = "arrtrix Bot"; + }; + as_token = ""; + hs_token = ""; + username_template = "arrtrix_{{.}}"; + }; + double_puppet = { + servers = {}; + secrets = {}; + }; + # By default, the following keys/secrets are set to `generate`. This would break when the service + # is restarted, since the previously generated configuration will be overwritten everytime. + # If encryption is enabled, it's recommended to set those keys via `environmentFile`. + encryption.pickle_key = ""; + provisioning.shared_secret = ""; + public_media.signing_key = ""; + direct_media.server_key = ""; + logging = { + min_level = "info"; + writers = lib.singleton { + type = "stdout"; + format = "pretty-colored"; + time_format = " "; + }; + }; + }; +in { + options.services.arrtrix = { + enable = mkEnableOption "Arr-focused Matrix appservice foundation"; + + package = mkPackageOption pkgs.${namespace} "arrtrix" {}; + + registerToSynapse = mkOption { + type = types.bool; + default = config.services.matrix-synapse.enable; + defaultText = lib.literalExpression '' + config.services.matrix-synapse.enable + ''; + description = '' + Whether to add the bridge's app service registration file to + `services.matrix-synapse.settings.app_service_config_files`. + ''; + }; + + settings = mkOption { + apply = lib.recursiveUpdate defaultConfig; + type = settingsFormat.type; + default = defaultConfig; + description = '' + {file}`config.yaml` configuration as a Nix attribute set. + Configuration options should match those described in the example configuration. + Get an example configuration by executing `arrtrix -c example.yaml --generate-example-config` + Secret tokens should be specified using {option}`environmentFile` + instead of this world-readable attribute set. + ''; + example = {}; + }; + + serviceDependencies = lib.mkOption { + type = with lib.types; listOf str; + default = + (lib.optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit) + ++ (lib.optional config.services.matrix-conduit.enable "conduit.service"); + defaultText = lib.literalExpression '' + (optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit) + ++ (optional config.services.matrix-conduit.enable "conduit.service") + ''; + description = '' + List of systemd units to require and wait for when starting the application service. + ''; + }; + }; + + config = mkIf cfg.enable { + users = { + users."arrtrix" = { + isSystemUser = true; + group = "arrtrix"; + }; + groups."arrtrix" = {}; + }; + + services.matrix-synapse = lib.mkIf cfg.registerToSynapse { + settings.app_service_config_files = [registrationFile]; + }; + systemd.services.matrix-synapse = lib.mkIf cfg.registerToSynapse { + serviceConfig.SupplementaryGroups = ["arrtrix"]; + }; + + systemd.services.arrtrix = { + description = "arrtrix, A *arr stack to matrix bridge for *arr-notifications"; + + wantedBy = ["multi-user.target"]; + after = ["network-online.target"]; + wants = ["network-online.target"]; + restartTriggers = [settingsFileUnsubstituted]; + + preStart = '' + # substitute the settings file by environment variables + # in this case read from EnvironmentFile + test -f '${settingsFile}' && rm -f '${settingsFile}' + + old_umask=$(umask) + umask 0177 + ${lib.getExe pkgs.envsubst} -o '${settingsFile}' -i '${settingsFileUnsubstituted}' + umask $old_umask + + if [ ! -f '${registrationFile}' ]; then + ${lib.getExe cfg.package} --generate-registration --config='${settingsFile}' --registration='${registrationFile}' + fi + chmod 640 ${registrationFile} + + # 1. Overwrite registration tokens in config + # 2. If environment variable MAUTRIX_SIGNAL_BRIDGE_LOGIN_SHARED_SECRET + # is set, set it as the login shared secret value for the configured + # homeserver domain. + umask 0177 + ${lib.getExe pkgs.yq} -s '.[0].appservice.as_token = .[1].as_token + | .[0].appservice.hs_token = .[1].hs_token + | .[0] + | if env.MAUTRIX_SIGNAL_BRIDGE_LOGIN_SHARED_SECRET then .double_puppet.secrets.[.homeserver.domain] = env.MAUTRIX_SIGNAL_BRIDGE_LOGIN_SHARED_SECRET else . end' \ + '${settingsFile}' '${registrationFile}' > '${settingsFile}.tmp' + mv '${settingsFile}.tmp' '${settingsFile}' + umask $old_umask + ''; + + serviceConfig = { + Type = "simple"; + User = "arrtrix"; + Group = "arrtrix"; + + StateDirectory = baseNameOf dataDir; + WorkingDirectory = dataDir; + + ExecStart = '' + ${lib.getExe cfg.package} --config='${settingsFile}' --registration='${registrationFile}' + ''; + + Restart = "on-failure"; + RestartSec = "30s"; + + NoNewPrivileges = true; + PrivateTmp = true; + ProtectHome = true; + ProtectSystem = "strict"; + ProtectClock = true; + ProtectControlGroups = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + LockPersonality = true; + MemoryDenyWriteExecute = true; + + SystemCallArchitectures = "native"; + SystemCallErrorNumber = "EPERM"; + SystemCallFilter = ["@system-service"]; + UMask = "0027"; + }; + }; + }; +} diff --git a/packages/arrtrix/cmd/arrtrix/main.go b/packages/arrtrix/cmd/arrtrix/main.go new file mode 100644 index 0000000..7958a39 --- /dev/null +++ b/packages/arrtrix/cmd/arrtrix/main.go @@ -0,0 +1,26 @@ +package main + +import ( + "maunium.net/go/mautrix/bridgev2/matrix/mxmain" + + "sneeuwvlok/packages/arrtrix/pkg/connector" +) + +var ( + Tag = "unknown" + Commit = "unknown" + BuildTime = "unknown" +) + +var m = mxmain.BridgeMain{ + Name: "arrtrix", + URL: "https://github.com/chris-kruining/sneeuwvlok", + Description: "An Arr-focused Matrix appservice bridge.", + Version: "0.1.0", + Connector: &connector.ArrtrixConnector{}, +} + +func main() { + m.InitVersion(Tag, Commit, BuildTime) + m.Run() +} diff --git a/packages/arrtrix/default.nix b/packages/arrtrix/default.nix new file mode 100644 index 0000000..81950f9 --- /dev/null +++ b/packages/arrtrix/default.nix @@ -0,0 +1,33 @@ +{ + buildGoModule, + lib, + olm, + versionCheckHook, +}: +buildGoModule rec { + pname = "arrtrix"; + version = "0.1.0"; + tag = "v0.1.0"; + + src = lib.cleanSource ./.; + + vendorHash = "sha256-FbatoXcxZcnqVUmoj/jeSMFO/iTmD8uga47MoTdGcRw="; + subPackages = ["cmd/arrtrix"]; + + buildInputs = [olm]; + + ldflags = [ + "-X main.Tag=${tag}" + ]; + + doInstallCheck = true; + nativeInstallCheckInputs = [versionCheckHook]; + + meta = { + description = "*arr-stack Matrix bridge"; + homepage = "https://github.com/chris-kruining/sneeuwvlok"; + license = lib.licenses.mit; + maintainers = []; + mainProgram = "arrtrix"; + }; +} diff --git a/packages/arrtrix/go.mod b/packages/arrtrix/go.mod new file mode 100644 index 0000000..eed27b5 --- /dev/null +++ b/packages/arrtrix/go.mod @@ -0,0 +1,43 @@ +module sneeuwvlok/packages/arrtrix + +go 1.25.0 + +require ( + go.mau.fi/util v0.9.7 + maunium.net/go/mautrix v0.26.4 +) + +require ( + github.com/kr/pretty v0.3.1 // indirect + github.com/rogpeppe/go-internal v1.10.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect +) + +require ( + filippo.io/edwards25519 v1.2.0 // indirect + github.com/coder/websocket v1.8.14 // indirect + github.com/coreos/go-systemd/v22 v22.6.0 // indirect + github.com/lib/pq v1.11.2 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-sqlite3 v1.14.34 // indirect + github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6 // indirect + github.com/rs/xid v1.6.0 // indirect + github.com/rs/zerolog v1.34.0 // indirect + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect + github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/match v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + github.com/yuin/goldmark v1.7.16 // indirect + go.mau.fi/zeroconfig v0.2.0 // indirect + golang.org/x/crypto v0.49.0 // indirect + golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect + golang.org/x/net v0.52.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.35.0 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + maunium.net/go/mauflag v1.0.0 // indirect +) diff --git a/packages/arrtrix/go.sum b/packages/arrtrix/go.sum new file mode 100644 index 0000000..d8e9404 --- /dev/null +++ b/packages/arrtrix/go.sum @@ -0,0 +1,91 @@ +filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= +filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= +github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= +github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= +github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs= +github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk= +github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6 h1:rh2lKw/P/EqHa724vYH2+VVQ1YnW4u6EOXl0PMAovZE= +github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= +github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= +github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE= +github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +go.mau.fi/util v0.9.7 h1:AWGNbJfz1zRcQOKeOEYhKUG2fT+/26Gy6kyqcH8tnBg= +go.mau.fi/util v0.9.7/go.mod h1:5T2f3ZWZFAGgmFwg3dGw7YK6kIsb9lryDzvynoR98pE= +go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= +go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA= +golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M= +maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA= +maunium.net/go/mautrix v0.26.4 h1:enHSnkf0L2V9+VnfJfNhKSReSW6pBKS/x3Su+v+Vovs= +maunium.net/go/mautrix v0.26.4/go.mod h1:YWw8NWTszsbyFAznboicBObwHPgTSLcuTbVX2kY7U2M= diff --git a/packages/arrtrix/pkg/connector/config.go b/packages/arrtrix/pkg/connector/config.go new file mode 100644 index 0000000..98a0916 --- /dev/null +++ b/packages/arrtrix/pkg/connector/config.go @@ -0,0 +1,18 @@ +package connector + +import ( + _ "embed" + + up "go.mau.fi/util/configupgrade" +) + +//go:embed example-config.yaml +var ExampleConfig string + +type Config struct{} + +func upgradeConfig(helper up.Helper) {} + +func (s *ArrtrixConnector) GetConfig() (string, any, up.Upgrader) { + return ExampleConfig, &s.Config, up.SimpleUpgrader(upgradeConfig) +} diff --git a/packages/arrtrix/pkg/connector/connector.go b/packages/arrtrix/pkg/connector/connector.go new file mode 100644 index 0000000..e90ed46 --- /dev/null +++ b/packages/arrtrix/pkg/connector/connector.go @@ -0,0 +1,107 @@ +package connector + +import ( + "context" + "fmt" + + "maunium.net/go/mautrix/bridgev2" + "maunium.net/go/mautrix/bridgev2/database" + "maunium.net/go/mautrix/bridgev2/networkid" + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" +) + +type ArrtrixConnector struct { + Bridge *bridgev2.Bridge + Config Config +} + +var _ bridgev2.NetworkConnector = (*ArrtrixConnector)(nil) + +func (s *ArrtrixConnector) GetName() bridgev2.BridgeName { + return bridgev2.BridgeName{ + DisplayName: "Arrtrix", + NetworkURL: "https://wiki.servarr.com/", + NetworkID: "arrtrix", + BeeperBridgeType: "arrtrix", + DefaultPort: 29329, + DefaultCommandPrefix: "!arr", + } +} + +func (s *ArrtrixConnector) Init(bridge *bridgev2.Bridge) { + s.Bridge = bridge +} + +func (s *ArrtrixConnector) Start(context.Context) error { + return nil +} + +func (s *ArrtrixConnector) GetDBMetaTypes() database.MetaTypes { + return database.MetaTypes{} +} + +func (s *ArrtrixConnector) GetCapabilities() *bridgev2.NetworkGeneralCapabilities { + return &bridgev2.NetworkGeneralCapabilities{} +} + +func (s *ArrtrixConnector) LoadUserLogin(_ context.Context, login *bridgev2.UserLogin) error { + login.Client = &ArrtrixClient{ + Main: s, + UserLogin: login, + } + return nil +} + +func (s *ArrtrixConnector) GetLoginFlows() []bridgev2.LoginFlow { + return nil +} + +func (s *ArrtrixConnector) CreateLogin(_ context.Context, _ *bridgev2.User, flowID string) (bridgev2.LoginProcess, error) { + return nil, fmt.Errorf("login flow %q is not implemented", flowID) +} + +func (s *ArrtrixConnector) GetBridgeInfoVersion() (info, capabilities int) { + return 1, 1 +} + +type ArrtrixClient struct { + Main *ArrtrixConnector + UserLogin *bridgev2.UserLogin +} + +var _ bridgev2.NetworkAPI = (*ArrtrixClient)(nil) + +func (c *ArrtrixClient) Connect(context.Context) {} + +func (c *ArrtrixClient) Disconnect() {} + +func (c *ArrtrixClient) IsLoggedIn() bool { + return false +} + +func (c *ArrtrixClient) LogoutRemote(context.Context) {} + +func (c *ArrtrixClient) IsThisUser(context.Context, networkid.UserID) bool { + return false +} + +func (c *ArrtrixClient) GetChatInfo(context.Context, *bridgev2.Portal) (*bridgev2.ChatInfo, error) { + return &bridgev2.ChatInfo{}, nil +} + +func (c *ArrtrixClient) GetUserInfo(context.Context, *bridgev2.Ghost) (*bridgev2.UserInfo, error) { + return &bridgev2.UserInfo{}, nil +} + +func (c *ArrtrixClient) GetCapabilities(context.Context, *bridgev2.Portal) *event.RoomFeatures { + return &event.RoomFeatures{} +} + +func (c *ArrtrixClient) HandleMatrixMessage(context.Context, *bridgev2.MatrixMessage) (*bridgev2.MatrixMessageResponse, error) { + return nil, fmt.Errorf("bridging Matrix messages is not implemented") +} + +func (c *ArrtrixClient) GenerateTransactionID(userID id.UserID, roomID id.RoomID, eventType event.Type) networkid.RawTransactionID { + return networkid.RawTransactionID("") +} diff --git a/packages/arrtrix/pkg/connector/example-config.yaml b/packages/arrtrix/pkg/connector/example-config.yaml new file mode 100644 index 0000000..63a205e --- /dev/null +++ b/packages/arrtrix/pkg/connector/example-config.yaml @@ -0,0 +1,7 @@ +# No network-specific config is required yet. +# +# Future Arr-specific runtime options, such as webhook handling, can be added +# here without changing the shared mautrix bridge CLI/runtime shape. +# +# The CLI-provided config file is still fully used by the bridge runtime for +# all shared sections like bridge, database, homeserver, and appservice. diff --git a/script/synapse/shared_secret b/script/synapse/shared_secret new file mode 100644 index 0000000..85fc69f --- /dev/null +++ b/script/synapse/shared_secret @@ -0,0 +1,3 @@ +#!/bin/bash + +pwgen -s 128 1 From eeedb5268a0122d3f1ad26d57f9518e99446a5f9 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Thu, 16 Apr 2026 08:08:55 +0200 Subject: [PATCH 100/108] Remove Vaultwarden package definition --- packages/vaultwarden/default.nix | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 packages/vaultwarden/default.nix diff --git a/packages/vaultwarden/default.nix b/packages/vaultwarden/default.nix deleted file mode 100644 index 243288b..0000000 --- a/packages/vaultwarden/default.nix +++ /dev/null @@ -1,29 +0,0 @@ -{ lib, stdenv, rustPlatform, fetchFromGitHub, openssl, pkg-config, postgresql, dbBackend ? "postgresql", ... }: -rustPlatform.buildRustPackage rec { - pname = "vaultwarden"; - version = "1.34.3"; - - src = fetchFromGitHub { - owner = "Timshel"; - repo = "vaultwarden"; - rev = "1.34.3"; - hash = "sha256-Dj0ySVRvBZ/57+UHas3VI8bi/0JBRqn0IW1Dq+405J0="; - }; - - cargoHash = "sha256-4sDagd2XGamBz1XvDj4ycRVJ0F+4iwHOPlj/RglNDqE="; - - # used for "Server Installed" version in admin panel - env.VW_VERSION = version; - - nativeBuildInputs = [ pkg-config ]; - buildInputs = - [ openssl ] - ++ lib.optional (dbBackend == "postgresql") postgresql; - - buildFeatures = dbBackend; - - meta = with lib; { - license = licenses.agpl3Only; - mainProgram = "vaultwarden"; - }; -} \ No newline at end of file From fe627f3aab7143dd36e2e39caf68f2e917e875c4 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Thu, 16 Apr 2026 09:06:57 +0200 Subject: [PATCH 101/108] Add Arrtrix runtime, config, onboarding, and webhook support - Implement runtime package for bridge startup, config loading, and env overrides - Add onboarding package for management room welcome messages - Add matrixcmd package for command processing and help - Add webhook package with Radarr webhook support and validation - Extend connector config for webhooks and validation - Update default config and example config for new options - Add tests for new packages and config validation - Change database type default to sqlite3-fk-wal --- .../nixos/temp/services/arrtrix/default.nix | 31 +- packages/arrtrix/cmd/arrtrix/main.go | 5 +- packages/arrtrix/pkg/config/config.go | 61 +++ packages/arrtrix/pkg/config/config_test.go | 122 +++++ packages/arrtrix/pkg/connector/config.go | 37 +- packages/arrtrix/pkg/connector/config_test.go | 23 + packages/arrtrix/pkg/connector/connector.go | 2 + .../arrtrix/pkg/connector/example-config.yaml | 15 +- packages/arrtrix/pkg/matrixcmd/help.go | 60 +++ packages/arrtrix/pkg/matrixcmd/help_test.go | 42 ++ packages/arrtrix/pkg/matrixcmd/processor.go | 204 +++++++++ packages/arrtrix/pkg/onboarding/welcome.go | 137 ++++++ .../arrtrix/pkg/onboarding/welcome_test.go | 56 +++ packages/arrtrix/pkg/runtime/envconfig.go | 206 +++++++++ packages/arrtrix/pkg/runtime/example.go | 69 +++ packages/arrtrix/pkg/runtime/main.go | 418 ++++++++++++++++++ packages/arrtrix/pkg/runtime/main_test.go | 30 ++ packages/arrtrix/pkg/webhook/radarr.go | 241 ++++++++++ packages/arrtrix/pkg/webhook/radarr_test.go | 131 ++++++ 19 files changed, 1855 insertions(+), 35 deletions(-) create mode 100644 packages/arrtrix/pkg/config/config.go create mode 100644 packages/arrtrix/pkg/config/config_test.go create mode 100644 packages/arrtrix/pkg/connector/config_test.go create mode 100644 packages/arrtrix/pkg/matrixcmd/help.go create mode 100644 packages/arrtrix/pkg/matrixcmd/help_test.go create mode 100644 packages/arrtrix/pkg/matrixcmd/processor.go create mode 100644 packages/arrtrix/pkg/onboarding/welcome.go create mode 100644 packages/arrtrix/pkg/onboarding/welcome_test.go create mode 100644 packages/arrtrix/pkg/runtime/envconfig.go create mode 100644 packages/arrtrix/pkg/runtime/example.go create mode 100644 packages/arrtrix/pkg/runtime/main.go create mode 100644 packages/arrtrix/pkg/runtime/main_test.go create mode 100644 packages/arrtrix/pkg/webhook/radarr.go create mode 100644 packages/arrtrix/pkg/webhook/radarr_test.go diff --git a/modules/nixos/temp/services/arrtrix/default.nix b/modules/nixos/temp/services/arrtrix/default.nix index dfd7b32..67ff0b9 100644 --- a/modules/nixos/temp/services/arrtrix/default.nix +++ b/modules/nixos/temp/services/arrtrix/default.nix @@ -15,13 +15,18 @@ settingsFormat = pkgs.formats.json {}; defaultConfig = { + network.webhooks.radarr = { + enabled = false; + path = "/_arrtrix/webhooks/radarr"; + secret = ""; + }; bridge = { command_prefix = "!arr"; relay.enabled = true; permissions."*" = "relay"; }; database = { - type = "sqlite3"; + type = "sqlite3-fk-wal"; uri = "file:${dataDir}/arrtrix.db?_txlock=immediate"; }; homeserver = { @@ -40,17 +45,6 @@ hs_token = ""; username_template = "arrtrix_{{.}}"; }; - double_puppet = { - servers = {}; - secrets = {}; - }; - # By default, the following keys/secrets are set to `generate`. This would break when the service - # is restarted, since the previously generated configuration will be overwritten everytime. - # If encryption is enabled, it's recommended to set those keys via `environmentFile`. - encryption.pickle_key = ""; - provisioning.shared_secret = ""; - public_media.signing_key = ""; - direct_media.server_key = ""; logging = { min_level = "info"; writers = lib.singleton { @@ -145,19 +139,6 @@ in { ${lib.getExe cfg.package} --generate-registration --config='${settingsFile}' --registration='${registrationFile}' fi chmod 640 ${registrationFile} - - # 1. Overwrite registration tokens in config - # 2. If environment variable MAUTRIX_SIGNAL_BRIDGE_LOGIN_SHARED_SECRET - # is set, set it as the login shared secret value for the configured - # homeserver domain. - umask 0177 - ${lib.getExe pkgs.yq} -s '.[0].appservice.as_token = .[1].as_token - | .[0].appservice.hs_token = .[1].hs_token - | .[0] - | if env.MAUTRIX_SIGNAL_BRIDGE_LOGIN_SHARED_SECRET then .double_puppet.secrets.[.homeserver.domain] = env.MAUTRIX_SIGNAL_BRIDGE_LOGIN_SHARED_SECRET else . end' \ - '${settingsFile}' '${registrationFile}' > '${settingsFile}.tmp' - mv '${settingsFile}.tmp' '${settingsFile}' - umask $old_umask ''; serviceConfig = { diff --git a/packages/arrtrix/cmd/arrtrix/main.go b/packages/arrtrix/cmd/arrtrix/main.go index 7958a39..3fa476f 100644 --- a/packages/arrtrix/cmd/arrtrix/main.go +++ b/packages/arrtrix/cmd/arrtrix/main.go @@ -1,9 +1,8 @@ package main import ( - "maunium.net/go/mautrix/bridgev2/matrix/mxmain" - "sneeuwvlok/packages/arrtrix/pkg/connector" + "sneeuwvlok/packages/arrtrix/pkg/runtime" ) var ( @@ -12,7 +11,7 @@ var ( BuildTime = "unknown" ) -var m = mxmain.BridgeMain{ +var m = runtime.Main{ Name: "arrtrix", URL: "https://github.com/chris-kruining/sneeuwvlok", Description: "An Arr-focused Matrix appservice bridge.", diff --git a/packages/arrtrix/pkg/config/config.go b/packages/arrtrix/pkg/config/config.go new file mode 100644 index 0000000..c3b11b8 --- /dev/null +++ b/packages/arrtrix/pkg/config/config.go @@ -0,0 +1,61 @@ +package config + +import ( + "go.mau.fi/util/dbutil" + "go.mau.fi/zeroconfig" + "gopkg.in/yaml.v3" + + "maunium.net/go/mautrix/bridgev2/bridgeconfig" +) + +type Config struct { + Network yaml.Node `yaml:"network"` + + Bridge bridgeconfig.BridgeConfig `yaml:"bridge"` + Database dbutil.Config `yaml:"database"` + Homeserver bridgeconfig.HomeserverConfig `yaml:"homeserver"` + AppService bridgeconfig.AppserviceConfig `yaml:"appservice"` + Logging zeroconfig.Config `yaml:"logging"` + + EnvConfigPrefix string `yaml:"env_config_prefix"` + ManagementTexts bridgeconfig.ManagementRoomTexts `yaml:"management_room_texts"` +} + +func Load(data []byte) (*Config, error) { + var cfg Config + if err := yaml.Unmarshal(data, &cfg); err != nil { + return nil, err + } + cfg.applyDefaults() + return &cfg, nil +} + +func (c *Config) applyDefaults() { + if c.Homeserver.Software == "" { + c.Homeserver.Software = bridgeconfig.SoftwareStandard + } +} + +func (c *Config) Compile() bridgeconfig.Config { + return bridgeconfig.Config{ + Network: c.Network, + Bridge: c.Bridge, + Database: c.Database, + Homeserver: c.Homeserver, + AppService: c.AppService, + Logging: c.Logging, + EnvConfigPrefix: c.EnvConfigPrefix, + ManagementRoomTexts: c.ManagementTexts, + Matrix: bridgeconfig.MatrixConfig{ + MessageStatusEvents: false, + DeliveryReceipts: false, + MessageErrorNotices: true, + SyncDirectChatList: false, + FederateRooms: true, + }, + DoublePuppet: bridgeconfig.DoublePuppetConfig{ + Servers: map[string]string{}, + Secrets: map[string]string{}, + }, + } +} diff --git a/packages/arrtrix/pkg/config/config_test.go b/packages/arrtrix/pkg/config/config_test.go new file mode 100644 index 0000000..dc08292 --- /dev/null +++ b/packages/arrtrix/pkg/config/config_test.go @@ -0,0 +1,122 @@ +package config + +import ( + "testing" + + "maunium.net/go/mautrix/bridgev2/bridgeconfig" +) + +func TestLoadDefaultsHomeserverSoftware(t *testing.T) { + cfg, err := Load([]byte(` +bridge: + command_prefix: "!arr" +homeserver: + address: http://127.0.0.1:8008 + domain: test.local +appservice: + id: arrtrix + bot: + username: arrtrixbot + displayname: Arrtrix Bot + username_template: arrtrix_{{.}} +database: + type: sqlite3-fk-wal + uri: file:arrtrix.db?_txlock=immediate +logging: + min_level: info + writers: + - type: stdout + format: pretty-colored +`)) + if err != nil { + t.Fatalf("Load returned error: %v", err) + } + + if cfg.Homeserver.Software != bridgeconfig.SoftwareStandard { + t.Fatalf("expected homeserver software default %q, got %q", bridgeconfig.SoftwareStandard, cfg.Homeserver.Software) + } +} + +func TestCompileSetsInternalDefaultsForHiddenSections(t *testing.T) { + cfg, err := Load([]byte(` +bridge: + command_prefix: "!arr" + permissions: + "*": relay +homeserver: + address: http://127.0.0.1:8008 + domain: test.local +appservice: + id: arrtrix + bot: + username: arrtrixbot + displayname: Arrtrix Bot + username_template: arrtrix_{{.}} +database: + type: sqlite3-fk-wal + uri: file:arrtrix.db?_txlock=immediate +logging: + min_level: info + writers: + - type: stdout + format: pretty-colored +`)) + if err != nil { + t.Fatalf("Load returned error: %v", err) + } + + runtimeCfg := cfg.Compile() + if !runtimeCfg.Matrix.MessageErrorNotices { + t.Fatalf("expected message error notices to stay enabled") + } + if !runtimeCfg.Matrix.FederateRooms { + t.Fatalf("expected federated rooms to stay enabled") + } + if runtimeCfg.DoublePuppet.Servers == nil || runtimeCfg.DoublePuppet.Secrets == nil { + t.Fatalf("expected hidden double puppet maps to be initialized") + } +} + +func TestLoadIgnoresLegacyHiddenSections(t *testing.T) { + cfg, err := Load([]byte(` +bridge: + command_prefix: "!arr" +homeserver: + address: http://127.0.0.1:8008 + domain: test.local +appservice: + id: arrtrix + bot: + username: arrtrixbot + displayname: Arrtrix Bot + username_template: arrtrix_{{.}} +database: + type: sqlite3-fk-wal + uri: file:arrtrix.db?_txlock=immediate +logging: + min_level: info + writers: + - type: stdout + format: pretty-colored +matrix: + federate_rooms: false +provisioning: + shared_secret: ignored +double_puppet: + secrets: + test.local: secret +encryption: + allow: true +`)) + if err != nil { + t.Fatalf("Load returned error: %v", err) + } + + runtimeCfg := cfg.Compile() + if !runtimeCfg.Matrix.FederateRooms { + t.Fatalf("expected runtime defaults to win for hidden legacy sections") + } + if len(runtimeCfg.DoublePuppet.Secrets) != 0 { + t.Fatalf("expected hidden double puppet secrets to stay internal-only") + } +} diff --git a/packages/arrtrix/pkg/connector/config.go b/packages/arrtrix/pkg/connector/config.go index 98a0916..3702cee 100644 --- a/packages/arrtrix/pkg/connector/config.go +++ b/packages/arrtrix/pkg/connector/config.go @@ -2,17 +2,52 @@ package connector import ( _ "embed" + "fmt" + "net/http" up "go.mau.fi/util/configupgrade" + "maunium.net/go/mautrix/bridgev2" + + "sneeuwvlok/packages/arrtrix/pkg/webhook" ) //go:embed example-config.yaml var ExampleConfig string -type Config struct{} +type Config struct { + Webhooks WebhooksConfig `yaml:"webhooks"` +} + +type WebhooksConfig struct { + Radarr webhook.RadarrConfig `yaml:"radarr"` +} + +func (c *Config) applyDefaults() { + c.Webhooks.Radarr.ApplyDefaults() +} + +func (c *Config) Validate() error { + return c.Webhooks.Radarr.Validate() +} func upgradeConfig(helper up.Helper) {} func (s *ArrtrixConnector) GetConfig() (string, any, up.Upgrader) { + s.Config.applyDefaults() return ExampleConfig, &s.Config, up.SimpleUpgrader(upgradeConfig) } + +func (s *ArrtrixConnector) ValidateConfig() error { + s.Config.applyDefaults() + return s.Config.Validate() +} + +func (s *ArrtrixConnector) MountRoutes(router *http.ServeMux) error { + s.Config.applyDefaults() + if s.Bridge == nil { + return fmt.Errorf("bridge is not initialized") + } + return webhook.MountRadarr(router, s.Bridge, s.Config.Webhooks.Radarr) +} + +var _ bridgev2.ConfigValidatingNetwork = (*ArrtrixConnector)(nil) diff --git a/packages/arrtrix/pkg/connector/config_test.go b/packages/arrtrix/pkg/connector/config_test.go new file mode 100644 index 0000000..5199308 --- /dev/null +++ b/packages/arrtrix/pkg/connector/config_test.go @@ -0,0 +1,23 @@ +package connector + +import "testing" + +func TestConfigDefaultsApplyRadarrWebhookPath(t *testing.T) { + var cfg Config + + cfg.applyDefaults() + + if cfg.Webhooks.Radarr.Path == "" { + t.Fatal("expected radarr webhook path default to be set") + } +} + +func TestConfigValidateRejectsEnabledWebhookWithoutSecret(t *testing.T) { + cfg := Config{} + cfg.Webhooks.Radarr.Enabled = true + cfg.applyDefaults() + + if err := cfg.Validate(); err == nil { + t.Fatal("expected missing secret to fail validation") + } +} diff --git a/packages/arrtrix/pkg/connector/connector.go b/packages/arrtrix/pkg/connector/connector.go index e90ed46..121e94c 100644 --- a/packages/arrtrix/pkg/connector/connector.go +++ b/packages/arrtrix/pkg/connector/connector.go @@ -3,6 +3,7 @@ package connector import ( "context" "fmt" + "net/http" "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2/database" @@ -17,6 +18,7 @@ type ArrtrixConnector struct { } var _ bridgev2.NetworkConnector = (*ArrtrixConnector)(nil) +var _ interface{ MountRoutes(*http.ServeMux) error } = (*ArrtrixConnector)(nil) func (s *ArrtrixConnector) GetName() bridgev2.BridgeName { return bridgev2.BridgeName{ diff --git a/packages/arrtrix/pkg/connector/example-config.yaml b/packages/arrtrix/pkg/connector/example-config.yaml index 63a205e..5fa52c6 100644 --- a/packages/arrtrix/pkg/connector/example-config.yaml +++ b/packages/arrtrix/pkg/connector/example-config.yaml @@ -1,7 +1,10 @@ -# No network-specific config is required yet. +# Arrtrix-specific runtime options. # -# Future Arr-specific runtime options, such as webhook handling, can be added -# here without changing the shared mautrix bridge CLI/runtime shape. -# -# The CLI-provided config file is still fully used by the bridge runtime for -# all shared sections like bridge, database, homeserver, and appservice. +webhooks: + radarr: + enabled: false + path: /_arrtrix/webhooks/radarr + secret: "" + # The first implementation delivers notifications to the only configured + # management room. If more than one management room exists, the webhook is + # rejected until routing is configured more explicitly. diff --git a/packages/arrtrix/pkg/matrixcmd/help.go b/packages/arrtrix/pkg/matrixcmd/help.go new file mode 100644 index 0000000..7da0d84 --- /dev/null +++ b/packages/arrtrix/pkg/matrixcmd/help.go @@ -0,0 +1,60 @@ +package matrixcmd + +import ( + "fmt" + "sort" + "strings" +) + +func NewHelpHandler(proc *Processor) Handler { + return NewHandler(Meta{ + Name: "help", + Description: "Show this help message.", + }, func(ctx *Context) { + ctx.Reply(formatHelp(proc, ctx)) + }) +} + +func formatHelp(proc *Processor, ctx *Context) string { + var builder strings.Builder + + switch { + case ctx.RoomID == ctx.User.ManagementRoom: + builder.WriteString(fmt.Sprintf("This is your management room: prefixing commands with `%s` is not required.\n", ctx.Bridge.Config.CommandPrefix)) + case ctx.Portal != nil: + builder.WriteString(fmt.Sprintf("**This is a portal room**: you must always prefix commands with `%s`. Management commands will not be bridged.\n", ctx.Bridge.Config.CommandPrefix)) + default: + builder.WriteString(fmt.Sprintf("This is not your management room: prefixing commands with `%s` is required.\n", ctx.Bridge.Config.CommandPrefix)) + } + + builder.WriteString("Parameters in [square brackets] are optional, while parameters in are required.\n\n") + builder.WriteString("#### General\n") + + handlers := proc.Handlers() + sort.SliceStable(handlers, func(i, j int) bool { + return handlers[i].Meta().Name < handlers[j].Meta().Name + }) + for _, handler := range handlers { + meta := handler.Meta() + builder.WriteString("**") + builder.WriteString(meta.Name) + builder.WriteString("**") + if meta.Usage != "" { + builder.WriteByte(' ') + builder.WriteString(meta.Usage) + } + if meta.Description != "" { + builder.WriteString(" - ") + builder.WriteString(meta.Description) + } + builder.WriteByte('\n') + } + + if extra := strings.TrimSpace(ctx.Processor.texts.AdditionalHelp); extra != "" { + builder.WriteByte('\n') + builder.WriteString(extra) + builder.WriteByte('\n') + } + + return builder.String() +} diff --git a/packages/arrtrix/pkg/matrixcmd/help_test.go b/packages/arrtrix/pkg/matrixcmd/help_test.go new file mode 100644 index 0000000..b5b325b --- /dev/null +++ b/packages/arrtrix/pkg/matrixcmd/help_test.go @@ -0,0 +1,42 @@ +package matrixcmd + +import ( + "strings" + "testing" + + "maunium.net/go/mautrix/bridgev2" + "maunium.net/go/mautrix/bridgev2/bridgeconfig" + "maunium.net/go/mautrix/bridgev2/database" + "maunium.net/go/mautrix/id" +) + +func TestFormatHelpManagementRoom(t *testing.T) { + roomID := id.RoomID("!arrtrix:test") + proc := &Processor{ + texts: bridgeconfig.ManagementRoomTexts{AdditionalHelp: "Extra help text."}, + command: make(map[string]Handler), + alias: make(map[string]string), + } + proc.Add(NewHelpHandler(proc)) + + out := formatHelp(proc, &Context{ + Bridge: &bridgev2.Bridge{ + Config: &bridgeconfig.BridgeConfig{ + CommandPrefix: "!arr", + }, + }, + RoomID: roomID, + User: &bridgev2.User{User: &database.User{ManagementRoom: roomID}}, + Processor: proc, + }) + + for _, fragment := range []string{ + "prefixing commands with `!arr` is not required", + "**help** - Show this help message.", + "Extra help text.", + } { + if !strings.Contains(out, fragment) { + t.Fatalf("expected help output to contain %q, got:\n%s", fragment, out) + } + } +} diff --git a/packages/arrtrix/pkg/matrixcmd/processor.go b/packages/arrtrix/pkg/matrixcmd/processor.go new file mode 100644 index 0000000..1dabfd6 --- /dev/null +++ b/packages/arrtrix/pkg/matrixcmd/processor.go @@ -0,0 +1,204 @@ +package matrixcmd + +import ( + "context" + "fmt" + "runtime/debug" + "sort" + "strings" + + "github.com/rs/zerolog" + + "maunium.net/go/mautrix/bridgev2" + "maunium.net/go/mautrix/bridgev2/bridgeconfig" + "maunium.net/go/mautrix/bridgev2/status" + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/format" + "maunium.net/go/mautrix/id" +) + +type Handler interface { + Meta() Meta + Run(*Context) +} + +type Meta struct { + Name string + Description string + Usage string + Aliases []string +} + +type HandlerFunc struct { + meta Meta + run func(*Context) +} + +func NewHandler(meta Meta, run func(*Context)) Handler { + return HandlerFunc{meta: meta, run: run} +} + +func (h HandlerFunc) Meta() Meta { + return h.meta +} + +func (h HandlerFunc) Run(ctx *Context) { + h.run(ctx) +} + +type Processor struct { + bridge *bridgev2.Bridge + bot bridgev2.MatrixAPI + texts bridgeconfig.ManagementRoomTexts + command map[string]Handler + alias map[string]string + order []string +} + +type Context struct { + Bridge *bridgev2.Bridge + Bot bridgev2.MatrixAPI + RoomID id.RoomID + OrigRoomID id.RoomID + EventID id.EventID + ReplyTo id.EventID + User *bridgev2.User + Portal *bridgev2.Portal + Command string + Args []string + RawArgs string + Ctx context.Context + Log *zerolog.Logger + Processor *Processor +} + +var _ bridgev2.CommandProcessor = (*Processor)(nil) + +func NewProcessor(bridge *bridgev2.Bridge, texts bridgeconfig.ManagementRoomTexts) *Processor { + proc := &Processor{ + bridge: bridge, + bot: bridge.Bot, + texts: texts, + command: make(map[string]Handler), + alias: make(map[string]string), + } + proc.Add(NewHelpHandler(proc)) + return proc +} + +func (p *Processor) Add(handler Handler) { + meta := handler.Meta() + p.command[meta.Name] = handler + p.order = append(p.order, meta.Name) + for _, alias := range meta.Aliases { + p.alias[alias] = meta.Name + } +} + +func (p *Processor) Handlers() []Handler { + names := append([]string(nil), p.order...) + sort.Strings(names) + + handlers := make([]Handler, 0, len(names)) + for _, name := range names { + handler, ok := p.command[name] + if ok { + handlers = append(handlers, handler) + } + } + return handlers +} + +func (p *Processor) Handle(ctx context.Context, roomID id.RoomID, eventID id.EventID, user *bridgev2.User, message string, replyTo id.EventID) { + ms := &bridgev2.MessageStatus{ + Step: status.MsgStepCommand, + Status: event.MessageStatusSuccess, + } + + logCopy := zerolog.Ctx(ctx).With().Logger() + log := &logCopy + + defer func() { + statusInfo := &bridgev2.MessageStatusEventInfo{ + RoomID: roomID, + SourceEventID: eventID, + EventType: event.EventMessage, + Sender: user.MXID, + } + + if recovered := recover(); recovered != nil { + logEvt := log.Error().Bytes(zerolog.ErrorStackFieldName, debug.Stack()) + if err, ok := recovered.(error); ok { + logEvt = logEvt.Err(err) + ms.InternalError = err + } else { + logEvt = logEvt.Any(zerolog.ErrorFieldName, recovered) + ms.InternalError = fmt.Errorf("%v", recovered) + } + logEvt.Msg("Panic in arrtrix Matrix command handler") + ms.Status = event.MessageStatusFail + ms.IsCertain = true + ms.ErrorAsMessage = true + } + + p.bridge.Matrix.SendMessageStatus(ctx, ms, statusInfo) + }() + + args := strings.Fields(message) + if len(args) == 0 { + args = []string{"unknown-command"} + } + + commandName := strings.ToLower(args[0]) + if actual, ok := p.alias[commandName]; ok { + commandName = actual + } + + portal, err := p.bridge.GetPortalByMXID(ctx, roomID) + if err != nil { + log.Err(err).Msg("Failed to get portal") + } + + commandCtx := &Context{ + Bridge: p.bridge, + Bot: p.bot, + RoomID: roomID, + OrigRoomID: roomID, + EventID: eventID, + ReplyTo: replyTo, + User: user, + Portal: portal, + Command: commandName, + Args: args[1:], + RawArgs: strings.TrimSpace(strings.TrimPrefix(message, args[0])), + Ctx: ctx, + Log: log, + Processor: p, + } + + handler, ok := p.command[commandName] + if !ok { + log.Debug().Str("mx_command", commandName).Msg("Received unknown Matrix room command") + commandCtx.Reply("Unknown command, use the `help` command for help.") + return + } + + log.UpdateContext(func(c zerolog.Context) zerolog.Context { + return c.Str("mx_command", commandName) + }) + log.Debug().Msg("Received Matrix room command") + handler.Run(commandCtx) +} + +func (c *Context) Reply(message string, args ...any) { + message = strings.ReplaceAll(message, "$cmdprefix ", c.Bridge.Config.CommandPrefix+" ") + if len(args) > 0 { + message = fmt.Sprintf(message, args...) + } + + content := format.RenderMarkdown(message, true, false) + content.MsgType = event.MsgNotice + if _, err := c.Bot.SendMessage(c.Ctx, c.OrigRoomID, event.EventMessage, &event.Content{Parsed: &content}, nil); err != nil { + c.Log.Err(err).Msg("Failed to reply to Matrix room command") + } +} diff --git a/packages/arrtrix/pkg/onboarding/welcome.go b/packages/arrtrix/pkg/onboarding/welcome.go new file mode 100644 index 0000000..14860c1 --- /dev/null +++ b/packages/arrtrix/pkg/onboarding/welcome.go @@ -0,0 +1,137 @@ +package onboarding + +import ( + "context" + "fmt" + "strings" + + "github.com/rs/zerolog" + + "maunium.net/go/mautrix/bridgev2" + "maunium.net/go/mautrix/bridgev2/bridgeconfig" + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/format" + "maunium.net/go/mautrix/id" +) + +const handledInviteEventType = "com.arrtrix.handled_invite" + +func HandleBotInvite(ctx context.Context, bridge *bridgev2.Bridge, texts bridgeconfig.ManagementRoomTexts, evt *event.Event) { + if evt.Type != event.StateMember || + evt.GetStateKey() != bridge.Bot.GetMXID().String() || + evt.Content.AsMember().Membership != event.MembershipInvite { + return + } + + log := zerolog.Ctx(ctx) + sender, err := bridge.GetUserByMXID(ctx, evt.Sender) + if err != nil { + log.Err(err).Msg("Failed to load sender for bot invite") + return + } + if !sender.Permissions.Commands { + return + } + + if err = bridge.Bot.EnsureJoined(ctx, evt.RoomID); err != nil { + log.Err(err).Msg("Failed to accept invite to room") + return + } + + members, err := bridge.Matrix.GetMembers(ctx, evt.RoomID) + if err != nil { + log.Err(err).Msg("Failed to get members of room after accepting invite") + return + } + if len(members) != 2 { + return + } + + assignedManagementRoom := sender.ManagementRoom == "" + if assignedManagementRoom { + sender.ManagementRoom = evt.RoomID + if err = sender.Save(ctx); err != nil { + log.Err(err).Msg("Failed to update user's management room in database") + return + } + } + + message := buildWelcomeMessage(bridge, texts, sender, assignedManagementRoom) + content := format.RenderMarkdown(message, true, false) + if _, err = bridge.Bot.SendMessage(ctx, evt.RoomID, event.EventMessage, &event.Content{Parsed: &content}, nil); err != nil { + log.Err(err).Msg("Failed to send welcome message to room") + return + } + + evt.Type = event.Type{Type: handledInviteEventType} +} + +func buildWelcomeMessage(bridge *bridgev2.Bridge, texts bridgeconfig.ManagementRoomTexts, sender *bridgev2.User, assignedManagementRoom bool) string { + return composeWelcomeMessage( + bridge.Network.GetName().DisplayName, + bridge.Config.CommandPrefix, + bridge.Bot.GetMXID(), + texts, + sender.GetDefaultLogin() != nil, + assignedManagementRoom, + ) +} + +func composeWelcomeMessage( + bridgeName string, + commandPrefix string, + botMXID id.UserID, + texts bridgeconfig.ManagementRoomTexts, + connected bool, + assignedManagementRoom bool, +) string { + replacer := strings.NewReplacer( + "$cmdprefix", commandPrefix, + "$bridge", bridgeName, + "$bot", string(botMXID), + ) + + var parts []string + + base := strings.TrimSpace(texts.Welcome) + if base == "" { + base = fmt.Sprintf("Hello, I'm the %s bot.", bridgeName) + } + parts = append(parts, replacer.Replace(base)) + + if assignedManagementRoom { + parts = append(parts, "This room has been marked as your management room.") + } else { + parts = append(parts, fmt.Sprintf("Use `%s help` to see available commands in this room.", commandPrefix)) + } + + if connected { + connected := strings.TrimSpace(texts.WelcomeConnected) + if connected == "" { + connected = "You're connected. Use `help` to see the commands available right now." + } + parts = append(parts, replacer.Replace(connected)) + } else { + unconnected := strings.TrimSpace(texts.WelcomeUnconnected) + if unconnected == "" { + unconnected = "Use `help` to see the commands available right now." + } + parts = append(parts, replacer.Replace(unconnected)) + } + + if extra := strings.TrimSpace(texts.AdditionalHelp); extra != "" { + parts = append(parts, replacer.Replace(extra)) + } + + return strings.Join(parts, "\n\n") +} + +func IsHandledInviteEvent(evt *event.Event) bool { + return evt.Type.Type == handledInviteEventType +} + +func IsBotInviteFor(roomBot id.UserID, evt *event.Event) bool { + return evt.Type == event.StateMember && + evt.GetStateKey() == roomBot.String() && + evt.Content.AsMember().Membership == event.MembershipInvite +} diff --git a/packages/arrtrix/pkg/onboarding/welcome_test.go b/packages/arrtrix/pkg/onboarding/welcome_test.go new file mode 100644 index 0000000..de6f42a --- /dev/null +++ b/packages/arrtrix/pkg/onboarding/welcome_test.go @@ -0,0 +1,56 @@ +package onboarding + +import ( + "strings" + "testing" + + "maunium.net/go/mautrix/bridgev2/bridgeconfig" + "maunium.net/go/mautrix/id" +) + +func TestComposeWelcomeMessageDefaults(t *testing.T) { + out := composeWelcomeMessage( + "Arrtrix", + "!arr", + id.UserID("@arrtrixbot:test"), + bridgeconfig.ManagementRoomTexts{}, + false, + true, + ) + + for _, fragment := range []string{ + "Hello, I'm the Arrtrix bot.", + "This room has been marked as your management room.", + "Use `help` to see the commands available right now.", + } { + if !strings.Contains(out, fragment) { + t.Fatalf("expected welcome output to contain %q, got:\n%s", fragment, out) + } + } +} + +func TestComposeWelcomeMessageTemplateValues(t *testing.T) { + out := composeWelcomeMessage( + "Arrtrix", + "!arr", + id.UserID("@arrtrixbot:test"), + bridgeconfig.ManagementRoomTexts{ + Welcome: "Welcome to $bridge.", + WelcomeConnected: "Talk to $bot with $cmdprefix help.", + AdditionalHelp: "Custom footer for $bridge.", + }, + true, + false, + ) + + for _, fragment := range []string{ + "Welcome to Arrtrix.", + "Use `!arr help` to see available commands in this room.", + "Talk to @arrtrixbot:test with !arr help.", + "Custom footer for Arrtrix.", + } { + if !strings.Contains(out, fragment) { + t.Fatalf("expected templated welcome output to contain %q, got:\n%s", fragment, out) + } + } +} diff --git a/packages/arrtrix/pkg/runtime/envconfig.go b/packages/arrtrix/pkg/runtime/envconfig.go new file mode 100644 index 0000000..f8ffd13 --- /dev/null +++ b/packages/arrtrix/pkg/runtime/envconfig.go @@ -0,0 +1,206 @@ +package runtime + +import ( + "fmt" + "os" + "reflect" + "strconv" + "strings" +) + +const fileEnvPrefix = "READFILE:" + +func updateConfigFromEnv(cfg, networkData any, prefix string) error { + if prefix == "" { + return nil + } + + cfgVal := reflect.ValueOf(cfg) + networkVal := reflect.ValueOf(networkData) + + for _, env := range os.Environ() { + if !strings.HasPrefix(env, prefix) { + continue + } + + keyValue := strings.SplitN(env, "=", 2) + if len(keyValue) != 2 { + continue + } + + key := strings.TrimPrefix(keyValue[0], prefix) + value := keyValue[1] + if strings.HasSuffix(key, "_FILE") { + key = strings.TrimSuffix(key, "_FILE") + value = fileEnvPrefix + value + } + + key = strings.ToLower(key) + if !strings.ContainsRune(key, '.') { + key = strings.ReplaceAll(key, "__", ".") + } + + path := strings.Split(key, ".") + field, ok := reflectGetFromMainOrNetwork(cfgVal, networkVal, path) + if !ok { + return fmt.Errorf("%s not found", formatKey(path)) + } + + if strings.HasPrefix(value, fileEnvPrefix) { + filePath := strings.TrimPrefix(value, fileEnvPrefix) + fileData, err := os.ReadFile(filePath) + if err != nil { + return fmt.Errorf("failed to read file %s for %s: %w", filePath, formatKey(path), err) + } + value = strings.TrimSpace(string(fileData)) + } + + if err := setReflectedValue(field, path, value); err != nil { + return err + } + } + + return nil +} + +type reflectedField struct { + value reflect.Value + valueKind reflect.Kind + remainingPath []string +} + +func formatKey(path []string) string { + return strings.Join(path, "->") +} + +func reflectGetFromMainOrNetwork(main, network reflect.Value, path []string) (*reflectedField, bool) { + if len(path) > 0 && path[0] == "network" { + return reflectGetYAML(network, path[1:]) + } + return reflectGetYAML(main, path) +} + +func reflectGetYAML(value reflect.Value, path []string) (*reflectedField, bool) { + if len(path) == 0 { + return &reflectedField{value: value, valueKind: value.Kind()}, true + } + if value.Kind() == reflect.Ptr { + value = value.Elem() + } + + switch value.Kind() { + case reflect.Map: + return &reflectedField{ + value: value, + valueKind: value.Type().Elem().Kind(), + remainingPath: path, + }, true + case reflect.Struct: + fields := reflect.VisibleFields(value.Type()) + for _, field := range fields { + if yamlFieldName(field) != path[0] { + continue + } + return reflectGetYAML(value.FieldByIndex(field.Index), path[1:]) + } + } + + return nil, false +} + +func yamlFieldName(field reflect.StructField) string { + parts := strings.SplitN(field.Tag.Get("yaml"), ",", 2) + switch name := parts[0]; { + case name == "-" && len(parts) == 1: + return "" + case name == "": + return strings.ToLower(field.Name) + default: + return name + } +} + +func setReflectedValue(field *reflectedField, path []string, raw string) error { + parsed, err := parseValue(field.valueKind, raw, path) + if err != nil { + return err + } + + value := field.value + if value.Kind() == reflect.Ptr { + if value.IsNil() { + value.Set(reflect.New(value.Type().Elem())) + } + value = value.Elem() + } + + if value.Kind() == reflect.Map { + if value.Type().Key().Kind() != reflect.String { + return fmt.Errorf("unsupported map key type %s in %s", value.Type().Key().Kind(), formatKey(path)) + } + key := strings.Join(field.remainingPath, ".") + value.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(parsed)) + return nil + } + + value.Set(reflect.ValueOf(parsed)) + return nil +} + +func parseValue(kind reflect.Kind, raw string, path []string) (any, error) { + switch kind { + case reflect.String: + return raw, nil + case reflect.Bool: + parsed, err := strconv.ParseBool(raw) + if err != nil { + return nil, fmt.Errorf("invalid value for %s: %w", formatKey(path), err) + } + return parsed, nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + parsed, err := strconv.ParseInt(raw, 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid value for %s: %w", formatKey(path), err) + } + switch kind { + case reflect.Int8: + return int8(parsed), nil + case reflect.Int16: + return int16(parsed), nil + case reflect.Int32: + return int32(parsed), nil + case reflect.Int64: + return parsed, nil + default: + return int(parsed), nil + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + parsed, err := strconv.ParseUint(raw, 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid value for %s: %w", formatKey(path), err) + } + switch kind { + case reflect.Uint8: + return uint8(parsed), nil + case reflect.Uint16: + return uint16(parsed), nil + case reflect.Uint32: + return uint32(parsed), nil + case reflect.Uint64: + return parsed, nil + default: + return uint(parsed), nil + } + case reflect.Float32, reflect.Float64: + parsed, err := strconv.ParseFloat(raw, 64) + if err != nil { + return nil, fmt.Errorf("invalid value for %s: %w", formatKey(path), err) + } + if kind == reflect.Float32 { + return float32(parsed), nil + } + return parsed, nil + default: + return nil, fmt.Errorf("unsupported type %s in %s", kind, formatKey(path)) + } +} diff --git a/packages/arrtrix/pkg/runtime/example.go b/packages/arrtrix/pkg/runtime/example.go new file mode 100644 index 0000000..1cba7b6 --- /dev/null +++ b/packages/arrtrix/pkg/runtime/example.go @@ -0,0 +1,69 @@ +package runtime + +import ( + "fmt" + "strings" + + "maunium.net/go/mautrix/bridgev2" +) + +func makeExampleConfig(networkName bridgev2.BridgeName, networkExample string) string { + var builder strings.Builder + + builder.WriteString("# Network-specific config options\n") + builder.WriteString("network:\n") + for _, line := range strings.Split(strings.TrimRight(networkExample, "\n"), "\n") { + if line == "" { + builder.WriteString(" \n") + continue + } + builder.WriteString(" ") + builder.WriteString(line) + builder.WriteByte('\n') + } + builder.WriteByte('\n') + + builder.WriteString(fmt.Sprintf(`bridge: + command_prefix: "%s" + permissions: + "*": relay + "@admin:example.com": admin + +database: + type: sqlite3-fk-wal + uri: file:arrtrix.db?_txlock=immediate + +homeserver: + address: http://example.localhost:8008 + domain: example.com + software: standard + +appservice: + address: http://localhost:%d + hostname: 127.0.0.1 + port: %d + id: %s + bot: + username: %s + displayname: %s + as_token: This value is generated when generating the registration + hs_token: This value is generated when generating the registration + username_template: %s_{{.}} + +logging: + min_level: info + writers: + - type: stdout + format: pretty-colored + +management_room_texts: + welcome: "" + welcome_connected: "" + welcome_unconnected: "" + additional_help: "" + +env_config_prefix: "" +`, networkName.DefaultCommandPrefix, networkName.DefaultPort, networkName.DefaultPort, networkName.NetworkID, "arrtrixbot", "Arrtrix Bot", networkName.NetworkID)) + + return builder.String() +} diff --git a/packages/arrtrix/pkg/runtime/main.go b/packages/arrtrix/pkg/runtime/main.go new file mode 100644 index 0000000..42e1495 --- /dev/null +++ b/packages/arrtrix/pkg/runtime/main.go @@ -0,0 +1,418 @@ +package runtime + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "os" + "os/signal" + "runtime" + "strings" + "syscall" + "time" + + "github.com/rs/zerolog" + "go.mau.fi/util/dbutil" + "go.mau.fi/util/exerrors" + "go.mau.fi/util/exzerolog" + "go.mau.fi/util/progver" + "gopkg.in/yaml.v3" + flag "maunium.net/go/mauflag" + "maunium.net/go/mautrix/appservice" + + "maunium.net/go/mautrix" + "maunium.net/go/mautrix/bridgev2" + "maunium.net/go/mautrix/bridgev2/bridgeconfig" + "maunium.net/go/mautrix/bridgev2/commands" + "maunium.net/go/mautrix/bridgev2/matrix" + "maunium.net/go/mautrix/event" + + arrconfig "sneeuwvlok/packages/arrtrix/pkg/config" + "sneeuwvlok/packages/arrtrix/pkg/matrixcmd" + "sneeuwvlok/packages/arrtrix/pkg/onboarding" +) + +var configPath = flag.MakeFull("c", "config", "The path to your config file.", "config.yaml").String() +var writeExampleConfig = flag.MakeFull("e", "generate-example-config", "Save the example config to the config path and quit.", "false").Bool() +var dontSaveConfig = flag.MakeFull("n", "no-update", "Don't save updated config to disk.", "false").Bool() +var registrationPath = flag.MakeFull("r", "registration", "The path where to save the appservice registration.", "registration.yaml").String() +var generateRegistration = flag.MakeFull("g", "generate-registration", "Generate registration and quit.", "false").Bool() +var version = flag.MakeFull("v", "version", "View bridge version and quit.", "false").Bool() +var versionJSON = flag.Make().LongKey("version-json").Usage("Print a JSON object representing the bridge version and quit.").Default("false").Bool() +var ignoreUnsupportedDatabase = flag.Make().LongKey("ignore-unsupported-database").Usage("Run even if the database schema is too new").Default("false").Bool() +var ignoreForeignTables = flag.Make().LongKey("ignore-foreign-tables").Usage("Run even if the database contains tables from other programs (like Synapse)").Default("false").Bool() +var ignoreUnsupportedServer = flag.Make().LongKey("ignore-unsupported-server").Usage("Run even if the Matrix homeserver is outdated").Default("false").Bool() +var wantHelp, _ = flag.MakeHelpFlag() + +type Main struct { + Name string + Description string + URL string + Version string + + Connector bridgev2.NetworkConnector + PostInit func() + PostStart func() + + Log *zerolog.Logger + DB *dbutil.Database + PublicConfig *arrconfig.Config + Config *bridgeconfig.Config + Matrix *matrix.Connector + Bridge *bridgev2.Bridge + + ConfigPath string + RegistrationPath string + SaveConfig bool + + ver progver.ProgramVersion + manualStop chan int +} + +type versionJSONOutput struct { + progver.ProgramVersion + + OS string + Arch string + + Mautrix struct { + Version string + Commit string + } +} + +type routeMounter interface { + MountRoutes(*http.ServeMux) error +} + +func (m *Main) Run() { + m.PreInit() + m.Init() + m.Start() + exitCode := m.WaitForInterrupt() + m.Stop() + os.Exit(exitCode) +} + +func (m *Main) PreInit() { + m.manualStop = make(chan int, 1) + flag.SetHelpTitles( + fmt.Sprintf("%s - %s", m.Name, m.Description), + fmt.Sprintf("%s [-hgvn] [-c ] [-r ]", m.Name), + ) + + err := flag.Parse() + m.ConfigPath = *configPath + m.RegistrationPath = *registrationPath + m.SaveConfig = !*dontSaveConfig + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, err) + flag.PrintHelp() + os.Exit(1) + } + + switch { + case *wantHelp: + flag.PrintHelp() + os.Exit(0) + case *version: + fmt.Println(m.ver.VersionDescription) + os.Exit(0) + case *versionJSON: + output := versionJSONOutput{ + ProgramVersion: m.ver, + OS: runtime.GOOS, + Arch: runtime.GOARCH, + } + output.Mautrix.Version = mautrix.Version + output.Mautrix.Commit = mautrix.Commit + _ = json.NewEncoder(os.Stdout).Encode(output) + os.Exit(0) + case *writeExampleConfig: + m.writeExampleConfig() + os.Exit(0) + } + + m.LoadConfig() + if *generateRegistration { + m.GenerateRegistration() + os.Exit(0) + } +} + +func (m *Main) writeExampleConfig() { + if *configPath != "-" { + if _, err := os.Stat(*configPath); !errors.Is(err, os.ErrNotExist) { + _, _ = fmt.Fprintln(os.Stderr, *configPath, "already exists, please remove it if you want to generate a new example") + os.Exit(1) + } + } + + networkExample, _, _ := m.Connector.GetConfig() + example := makeExampleConfig(m.Connector.GetName(), networkExample) + if *configPath == "-" { + fmt.Print(example) + return + } + + exerrors.PanicIfNotNil(os.WriteFile(*configPath, []byte(example), 0o600)) + fmt.Println("Wrote example config to", *configPath) +} + +func (m *Main) GenerateRegistration() { + if !m.SaveConfig { + _, _ = fmt.Fprintln(os.Stderr, "--no-update is not compatible with --generate-registration") + os.Exit(5) + } + if m.Config.Homeserver.Domain == "example.com" { + _, _ = fmt.Fprintln(os.Stderr, "Homeserver domain is not set") + os.Exit(20) + } + + registration := m.Config.GenerateRegistration() + if err := registration.Save(m.RegistrationPath); err != nil { + _, _ = fmt.Fprintln(os.Stderr, "Failed to save registration:", err) + os.Exit(21) + } + + if err := m.saveConfig(); err != nil { + _, _ = fmt.Fprintln(os.Stderr, "Failed to save config:", err) + os.Exit(22) + } + + fmt.Println("Registration generated. See https://docs.mau.fi/bridges/general/registering-appservices.html for instructions on installing the registration.") +} + +func (m *Main) LoadConfig() { + configData, err := os.ReadFile(m.ConfigPath) + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, "Failed to read config:", err) + os.Exit(10) + } + + publicConfig, err := arrconfig.Load(configData) + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, "Failed to parse config:", err) + os.Exit(10) + } + cfg := publicConfig.Compile() + if err = m.loadRegistrationTokens(&cfg); err != nil { + _, _ = fmt.Fprintln(os.Stderr, "Failed to parse registration:", err) + os.Exit(10) + } + + _, networkData, _ := m.Connector.GetConfig() + if networkData != nil { + if err = cfg.Network.Decode(networkData); err != nil { + _, _ = fmt.Fprintln(os.Stderr, "Failed to parse network config:", err) + os.Exit(10) + } + } + + cfg.Bridge.Backfill = cfg.Backfill + if err = updateConfigFromEnv(&cfg, networkData, cfg.EnvConfigPrefix); err != nil { + _, _ = fmt.Fprintln(os.Stderr, "Failed to parse environment variables:", err) + os.Exit(10) + } + + m.PublicConfig = publicConfig + m.Config = &cfg +} + +func (m *Main) loadRegistrationTokens(cfg *bridgeconfig.Config) error { + if m.RegistrationPath == "" { + return nil + } + + data, err := os.ReadFile(m.RegistrationPath) + if errors.Is(err, os.ErrNotExist) { + return nil + } else if err != nil { + return err + } + + var tokens struct { + AppToken string `yaml:"as_token"` + ServerToken string `yaml:"hs_token"` + } + if err = yaml.Unmarshal(data, &tokens); err != nil { + return err + } + + if tokens.AppToken != "" { + cfg.AppService.ASToken = tokens.AppToken + } + if tokens.ServerToken != "" { + cfg.AppService.HSToken = tokens.ServerToken + } + return nil +} + +func (m *Main) Init() { + var err error + m.Log, err = m.Config.Logging.Compile() + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, "Failed to initialize logger:", err) + os.Exit(12) + } + exzerolog.SetupDefaults(m.Log) + + if err = m.validateConfig(); err != nil { + m.Log.WithLevel(zerolog.FatalLevel).Err(err).Msg("Configuration error") + m.Log.Info().Msg("See https://docs.mau.fi/faq/field-unconfigured for more info") + os.Exit(11) + } + + m.Log.Info(). + Str("name", m.Name). + Str("version", m.ver.FormattedVersion). + Time("built_at", m.ver.BuildTime). + Str("go_version", runtime.Version()). + Msg("Initializing bridge") + + m.initDB() + m.Matrix = matrix.NewConnector(m.Config) + m.Matrix.OnWebsocketReplaced = func() { + m.TriggerStop(0) + } + m.Matrix.IgnoreUnsupportedServer = *ignoreUnsupportedServer + m.Bridge = bridgev2.NewBridge("", m.DB, *m.Log, &m.Config.Bridge, m.Matrix, m.Connector, commands.NewProcessor) + m.Bridge.Commands = matrixcmd.NewProcessor(m.Bridge, m.Config.ManagementRoomTexts) + + if m.Matrix.EventProcessor != nil { + if m.Config.AppService.AsyncTransactions { + m.Matrix.EventProcessor.ExecMode = appservice.AsyncLoop + } else { + m.Matrix.EventProcessor.ExecMode = appservice.Sync + } + m.Matrix.EventProcessor.PrependHandler(event.StateMember, func(ctx context.Context, evt *event.Event) { + onboarding.HandleBotInvite(ctx, m.Bridge, m.Config.ManagementRoomTexts, evt) + }) + } + + m.Matrix.AS.DoublePuppetValue = m.Name + if mounter, ok := m.Connector.(routeMounter); ok { + if err = mounter.MountRoutes(m.Matrix.AS.Router); err != nil { + _, _ = fmt.Fprintln(os.Stderr, "Failed to mount HTTP routes:", err) + os.Exit(13) + } + } + + if m.PostInit != nil { + m.PostInit() + } +} + +func (m *Main) Start() { + ctx := m.Log.WithContext(context.Background()) + if err := m.Bridge.Start(ctx); err != nil { + m.Log.Fatal().Err(err).Msg("Failed to start bridge") + } + if m.PostStart != nil { + m.PostStart() + } +} + +func (m *Main) Stop() { + m.Bridge.StopWithTimeout(5 * time.Second) +} + +func (m *Main) WaitForInterrupt() int { + interrupts := make(chan os.Signal, 1) + signal.Notify(interrupts, os.Interrupt, syscall.SIGTERM) + select { + case <-interrupts: + m.Log.Info().Msg("Interrupt signal received from OS") + return 0 + case exitCode := <-m.manualStop: + m.Log.Info().Msg("Internal stop signal received") + return exitCode + } +} + +func (m *Main) TriggerStop(exitCode int) { + select { + case m.manualStop <- exitCode: + default: + } +} + +func (m *Main) InitVersion(tag, commit, rawBuildTime string) { + m.ver = progver.ProgramVersion{ + Name: m.Name, + URL: m.URL, + BaseVersion: m.Version, + }.Init(tag, commit, rawBuildTime) + mautrix.DefaultUserAgent = fmt.Sprintf("%s/%s %s", m.Name, m.ver.FormattedVersion, mautrix.DefaultUserAgent) + m.Version = m.ver.FormattedVersion +} + +func (m *Main) validateConfig() error { + switch { + case m.Config.Homeserver.Address == "http://example.localhost:8008": + return errors.New("homeserver.address not configured") + case m.Config.Homeserver.Domain == "example.com": + return errors.New("homeserver.domain not configured") + case !bridgeconfig.AllowedHomeserverSoftware[m.Config.Homeserver.Software]: + return errors.New("invalid value for homeserver.software (use `standard` if you don't know what the field is for)") + case m.Config.AppService.ASToken == "This value is generated when generating the registration": + return errors.New("appservice.as_token not configured. Did you forget to generate the registration?") + case m.Config.AppService.HSToken == "This value is generated when generating the registration": + return errors.New("appservice.hs_token not configured. Did you forget to generate the registration?") + case m.Config.Database.URI == "postgres://user:password@host/database?sslmode=disable": + return errors.New("database.uri not configured") + case !m.Config.Bridge.Permissions.IsConfigured(): + return errors.New("bridge.permissions not configured") + case !strings.Contains(m.Config.AppService.FormatUsername("1234567890"), "1234567890"): + return errors.New("username template is missing user ID placeholder") + default: + if validator, ok := m.Connector.(bridgev2.ConfigValidatingNetwork); ok { + return validator.ValidateConfig() + } + return nil + } +} + +func (m *Main) initDB() { + if m.Config.Database.Type == "sqlite3" { + m.Log.WithLevel(zerolog.FatalLevel).Msg("Invalid database type sqlite3. Use sqlite3-fk-wal instead.") + os.Exit(14) + } + if (m.Config.Database.Type == "sqlite3-fk-wal" || m.Config.Database.Type == "litestream") && + m.Config.Database.MaxOpenConns != 1 && + !strings.Contains(m.Config.Database.URI, "_txlock=immediate") { + var fixedURI string + switch { + case !strings.HasPrefix(m.Config.Database.URI, "file:"): + fixedURI = fmt.Sprintf("file:%s?_txlock=immediate", m.Config.Database.URI) + case !strings.ContainsRune(m.Config.Database.URI, '?'): + fixedURI = fmt.Sprintf("%s?_txlock=immediate", m.Config.Database.URI) + default: + fixedURI = fmt.Sprintf("%s&_txlock=immediate", m.Config.Database.URI) + } + m.Log.Warn().Str("fixed_uri_example", fixedURI).Msg("Using SQLite without _txlock=immediate is not recommended") + } + + var err error + m.DB, err = dbutil.NewFromConfig("megabridge/"+m.Name, m.Config.Database, dbutil.ZeroLogger(m.Log.With().Str("db_section", "main").Logger())) + if err != nil { + m.Log.WithLevel(zerolog.FatalLevel).Err(err).Msg("Failed to initialize database connection") + os.Exit(14) + } + m.DB.IgnoreUnsupportedDatabase = *ignoreUnsupportedDatabase + m.DB.IgnoreForeignTables = *ignoreForeignTables +} + +func (m *Main) saveConfig() error { + publicConfig := *m.PublicConfig + publicConfig.AppService.ASToken = m.Config.AppService.ASToken + publicConfig.AppService.HSToken = m.Config.AppService.HSToken + + configData, err := yaml.Marshal(&publicConfig) + if err != nil { + return err + } + return os.WriteFile(m.ConfigPath, configData, 0o600) +} diff --git a/packages/arrtrix/pkg/runtime/main_test.go b/packages/arrtrix/pkg/runtime/main_test.go new file mode 100644 index 0000000..f54201b --- /dev/null +++ b/packages/arrtrix/pkg/runtime/main_test.go @@ -0,0 +1,30 @@ +package runtime + +import ( + "os" + "path/filepath" + "testing" + + "maunium.net/go/mautrix/bridgev2/bridgeconfig" +) + +func TestLoadRegistrationTokens(t *testing.T) { + tempDir := t.TempDir() + registrationPath := filepath.Join(tempDir, "registration.yaml") + if err := os.WriteFile(registrationPath, []byte("as_token: app-token\nhs_token: hs-token\n"), 0o600); err != nil { + t.Fatalf("failed to write registration file: %v", err) + } + + cfg := &bridgeconfig.Config{} + main := &Main{RegistrationPath: registrationPath} + if err := main.loadRegistrationTokens(cfg); err != nil { + t.Fatalf("loadRegistrationTokens returned error: %v", err) + } + + if cfg.AppService.ASToken != "app-token" { + t.Fatalf("expected as token to be loaded, got %q", cfg.AppService.ASToken) + } + if cfg.AppService.HSToken != "hs-token" { + t.Fatalf("expected hs token to be loaded, got %q", cfg.AppService.HSToken) + } +} diff --git a/packages/arrtrix/pkg/webhook/radarr.go b/packages/arrtrix/pkg/webhook/radarr.go new file mode 100644 index 0000000..6f74342 --- /dev/null +++ b/packages/arrtrix/pkg/webhook/radarr.go @@ -0,0 +1,241 @@ +package webhook + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "strings" + + "maunium.net/go/mautrix/bridgev2" + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/format" + "maunium.net/go/mautrix/id" +) + +const ( + defaultRadarrWebhookPath = "/_arrtrix/webhooks/radarr" + radarrSecretHeader = "X-Arrtrix-Webhook-Secret" +) + +var ( + ErrNoManagementRoom = errors.New("no management room configured") + ErrAmbiguousManagementRoom = errors.New("multiple management rooms configured") +) + +type RadarrConfig struct { + Enabled bool `yaml:"enabled"` + Path string `yaml:"path"` + Secret string `yaml:"secret"` +} + +type radarrPayload struct { + EventType string `json:"eventType"` + Movie *radarrMovie `json:"movie"` + MovieFile *radarrMovieFile `json:"movieFile"` + IsUpgrade bool `json:"isUpgrade"` +} + +type radarrMovie struct { + Title string `json:"title"` + Year int `json:"year"` + ImdbID string `json:"imdbId"` + TmdbID int `json:"tmdbId"` + Path string `json:"path"` +} + +type radarrMovieFile struct { + Quality string `json:"quality"` + RelativePath string `json:"relativePath"` + SceneName string `json:"sceneName"` + ReleaseGroup string `json:"releaseGroup"` +} + +type roomResolver interface { + ResolveManagementRoom(context.Context) (id.RoomID, error) +} + +type noticeSender interface { + SendNotice(context.Context, id.RoomID, string) error +} + +type RadarrHandler struct { + config RadarrConfig + resolver roomResolver + sender noticeSender +} + +func (c *RadarrConfig) ApplyDefaults() { + if c.Path == "" { + c.Path = defaultRadarrWebhookPath + } +} + +func (c *RadarrConfig) Validate() error { + c.ApplyDefaults() + if !c.Enabled { + return nil + } + if !strings.HasPrefix(c.Path, "/") { + return fmt.Errorf("network.webhooks.radarr.path must start with /") + } + if strings.TrimSpace(c.Secret) == "" { + return fmt.Errorf("network.webhooks.radarr.secret must be set when the webhook is enabled") + } + return nil +} + +func MountRadarr(router *http.ServeMux, bridge *bridgev2.Bridge, cfg RadarrConfig) error { + cfg.ApplyDefaults() + if !cfg.Enabled { + return nil + } + if err := cfg.Validate(); err != nil { + return err + } + + handler := &RadarrHandler{ + config: cfg, + resolver: bridgeRoomResolver{bridge: bridge}, + sender: bridgeNoticeSender{bridge: bridge}, + } + router.Handle(fmt.Sprintf("POST %s", cfg.Path), handler) + return nil +} + +func (h *RadarrHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if !authorized(r, h.config.Secret) { + http.Error(w, "invalid webhook secret", http.StatusUnauthorized) + return + } + + var payload radarrPayload + if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { + http.Error(w, "invalid webhook payload", http.StatusBadRequest) + return + } + if strings.TrimSpace(payload.EventType) == "" { + http.Error(w, "missing eventType", http.StatusBadRequest) + return + } + + roomID, err := h.resolver.ResolveManagementRoom(r.Context()) + if err != nil { + status := http.StatusInternalServerError + if errors.Is(err, ErrNoManagementRoom) || errors.Is(err, ErrAmbiguousManagementRoom) { + status = http.StatusConflict + } + http.Error(w, err.Error(), status) + return + } + + if err = h.sender.SendNotice(r.Context(), roomID, renderRadarrNotice(payload)); err != nil { + http.Error(w, "failed to deliver webhook", http.StatusBadGateway) + return + } + + w.WriteHeader(http.StatusAccepted) +} + +type bridgeRoomResolver struct { + bridge *bridgev2.Bridge +} + +func (r bridgeRoomResolver) ResolveManagementRoom(ctx context.Context) (id.RoomID, error) { + rows, err := r.bridge.DB.Query(ctx, `SELECT mxid, management_room FROM "user" WHERE bridge_id=$1 AND management_room IS NOT NULL AND management_room <> ''`, r.bridge.ID) + if err != nil { + return "", fmt.Errorf("failed to query management rooms: %w", err) + } + defer rows.Close() + + var roomID id.RoomID + var owners []id.UserID + for rows.Next() { + var mxid, managementRoom string + if err = rows.Scan(&mxid, &managementRoom); err != nil { + return "", fmt.Errorf("failed to scan management room: %w", err) + } + owners = append(owners, id.UserID(mxid)) + if roomID == "" { + roomID = id.RoomID(managementRoom) + } + } + if err = rows.Err(); err != nil { + return "", fmt.Errorf("failed to iterate management rooms: %w", err) + } + switch len(owners) { + case 0: + return "", ErrNoManagementRoom + case 1: + return roomID, nil + default: + return "", fmt.Errorf("%w: %s", ErrAmbiguousManagementRoom, strings.Join(convertUserIDs(owners), ", ")) + } +} + +type bridgeNoticeSender struct { + bridge *bridgev2.Bridge +} + +func (s bridgeNoticeSender) SendNotice(ctx context.Context, roomID id.RoomID, markdown string) error { + if err := s.bridge.Bot.EnsureJoined(ctx, roomID); err != nil { + return err + } + content := format.RenderMarkdown(markdown, true, false) + _, err := s.bridge.Bot.SendMessage(ctx, roomID, event.EventMessage, &event.Content{Parsed: &content}, nil) + return err +} + +func authorized(r *http.Request, secret string) bool { + if secret == "" { + return true + } + if r.Header.Get(radarrSecretHeader) == secret { + return true + } + if bearer := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer "); bearer == secret && bearer != r.Header.Get("Authorization") { + return true + } + return r.URL.Query().Get("secret") == secret +} + +func renderRadarrNotice(payload radarrPayload) string { + title := "Radarr" + if payload.Movie != nil { + title = payload.Movie.Title + if payload.Movie.Year != 0 { + title = fmt.Sprintf("%s (%d)", title, payload.Movie.Year) + } + } + + lines := []string{fmt.Sprintf("**Radarr %s**", payload.EventType)} + if title != "Radarr" { + lines = append(lines, fmt.Sprintf("Movie: %s", title)) + } + if payload.MovieFile != nil && payload.MovieFile.Quality != "" { + lines = append(lines, fmt.Sprintf("Quality: %s", payload.MovieFile.Quality)) + } + if payload.MovieFile != nil && payload.MovieFile.RelativePath != "" { + lines = append(lines, fmt.Sprintf("File: `%s`", payload.MovieFile.RelativePath)) + } + if payload.EventType == "Download" { + lines = append(lines, fmt.Sprintf("Upgrade: %t", payload.IsUpgrade)) + } + if payload.Movie != nil && payload.Movie.ImdbID != "" { + lines = append(lines, fmt.Sprintf("IMDb: `%s`", payload.Movie.ImdbID)) + } + return strings.Join(lines, "\n") +} + +func convertUserIDs(users []id.UserID) []string { + out := make([]string, len(users)) + for i, user := range users { + out[i] = string(user) + } + return out +} + +var _ roomResolver = bridgeRoomResolver{} +var _ noticeSender = bridgeNoticeSender{} +var _ http.Handler = (*RadarrHandler)(nil) diff --git a/packages/arrtrix/pkg/webhook/radarr_test.go b/packages/arrtrix/pkg/webhook/radarr_test.go new file mode 100644 index 0000000..d4fc962 --- /dev/null +++ b/packages/arrtrix/pkg/webhook/radarr_test.go @@ -0,0 +1,131 @@ +package webhook + +import ( + "context" + "errors" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "maunium.net/go/mautrix/id" +) + +type stubRoomResolver struct { + roomID id.RoomID + err error +} + +func (s stubRoomResolver) ResolveManagementRoom(context.Context) (id.RoomID, error) { + return s.roomID, s.err +} + +type stubNoticeSender struct { + roomID id.RoomID + message string + err error +} + +func (s *stubNoticeSender) SendNotice(_ context.Context, roomID id.RoomID, message string) error { + s.roomID = roomID + s.message = message + return s.err +} + +func TestRadarrConfigDefaultsAndValidation(t *testing.T) { + cfg := RadarrConfig{Enabled: true, Secret: "secret"} + cfg.ApplyDefaults() + if cfg.Path != defaultRadarrWebhookPath { + t.Fatalf("expected default path %q, got %q", defaultRadarrWebhookPath, cfg.Path) + } + if err := cfg.Validate(); err != nil { + t.Fatalf("expected config to validate, got %v", err) + } +} + +func TestRadarrConfigRequiresSecretWhenEnabled(t *testing.T) { + cfg := RadarrConfig{Enabled: true} + if err := cfg.Validate(); err == nil { + t.Fatal("expected missing secret to fail validation") + } +} + +func TestRadarrHandlerRejectsUnauthorizedRequests(t *testing.T) { + handler := &RadarrHandler{ + config: RadarrConfig{Enabled: true, Secret: "secret"}, + resolver: stubRoomResolver{roomID: "!room:test"}, + sender: &stubNoticeSender{}, + } + + req := httptest.NewRequest(http.MethodPost, defaultRadarrWebhookPath, strings.NewReader(`{"eventType":"Test"}`)) + rec := httptest.NewRecorder() + handler.ServeHTTP(rec, req) + + if rec.Code != http.StatusUnauthorized { + t.Fatalf("expected unauthorized status, got %d", rec.Code) + } +} + +func TestRadarrHandlerDeliversNotice(t *testing.T) { + sender := &stubNoticeSender{} + handler := &RadarrHandler{ + config: RadarrConfig{Enabled: true, Secret: "secret"}, + resolver: stubRoomResolver{roomID: "!room:test"}, + sender: sender, + } + + req := httptest.NewRequest(http.MethodPost, defaultRadarrWebhookPath+"?secret=secret", strings.NewReader(`{"eventType":"Download","movie":{"title":"Dune","year":2021,"imdbId":"tt1160419"},"movieFile":{"quality":"1080p","relativePath":"Dune (2021)/Dune.mkv"},"isUpgrade":false}`)) + rec := httptest.NewRecorder() + handler.ServeHTTP(rec, req) + + if rec.Code != http.StatusAccepted { + t.Fatalf("expected accepted status, got %d", rec.Code) + } + if sender.roomID != "!room:test" { + t.Fatalf("expected notice sent to management room, got %q", sender.roomID) + } + if !strings.Contains(sender.message, "**Radarr Download**") || !strings.Contains(sender.message, "Dune (2021)") { + t.Fatalf("unexpected message: %s", sender.message) + } +} + +func TestRadarrHandlerReportsAmbiguousManagementRoom(t *testing.T) { + handler := &RadarrHandler{ + config: RadarrConfig{Enabled: true, Secret: "secret"}, + resolver: stubRoomResolver{err: ErrAmbiguousManagementRoom}, + sender: &stubNoticeSender{}, + } + + req := httptest.NewRequest(http.MethodPost, defaultRadarrWebhookPath, strings.NewReader(`{"eventType":"Test"}`)) + req.Header.Set(radarrSecretHeader, "secret") + rec := httptest.NewRecorder() + handler.ServeHTTP(rec, req) + + if rec.Code != http.StatusConflict { + t.Fatalf("expected conflict status, got %d", rec.Code) + } +} + +func TestRenderRadarrNoticeForTestEvent(t *testing.T) { + msg := renderRadarrNotice(radarrPayload{EventType: "Test"}) + if strings.TrimSpace(msg) != "**Radarr Test**" { + t.Fatalf("unexpected test-event message: %q", msg) + } +} + +func TestRadarrHandlerReturnsBadGatewayOnSendFailure(t *testing.T) { + handler := &RadarrHandler{ + config: RadarrConfig{Enabled: true, Secret: "secret"}, + resolver: stubRoomResolver{roomID: "!room:test"}, + sender: &stubNoticeSender{err: errors.New("send failed")}, + } + + req := httptest.NewRequest(http.MethodPost, defaultRadarrWebhookPath, strings.NewReader(`{"eventType":"Test"}`)) + req.Header.Set(radarrSecretHeader, "secret") + rec := httptest.NewRecorder() + handler.ServeHTTP(rec, req) + + if rec.Code != http.StatusBadGateway { + t.Fatalf("expected bad gateway status, got %d", rec.Code) + } +} From bbfe6867c8bf9a2452f611e1992d918b705bd2e3 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Thu, 16 Apr 2026 09:47:00 +0200 Subject: [PATCH 102/108] Refactor arrtrix webhook to use fixed path and remove legacy config - Switch arrtrix webhook to a fixed path: /_arrtrix/webhook - Remove Radarr-specific and secret-based config from arrtrix - Simplify connector and webhook handler logic - Update NixOS module to drop legacy webhook config - Add new tests for generic arrtrix webhook handler --- .editorconfig | 6 + .gitattributes | 5 +- .../services/communication/matrix/default.nix | 13 +- .../nixos/temp/services/arrtrix/default.nix | 5 - packages/arrtrix/pkg/config/config_test.go | 37 +++++ packages/arrtrix/pkg/connector/config.go | 23 +-- packages/arrtrix/pkg/connector/config_test.go | 23 --- .../arrtrix/pkg/connector/example-config.yaml | 12 +- .../arrtrix/pkg/webhook/{radarr.go => arr.go} | 127 +++++------------ packages/arrtrix/pkg/webhook/arr_test.go | 114 +++++++++++++++ packages/arrtrix/pkg/webhook/radarr_test.go | 131 ------------------ 11 files changed, 211 insertions(+), 285 deletions(-) create mode 100644 .editorconfig delete mode 100644 packages/arrtrix/pkg/connector/config_test.go rename packages/arrtrix/pkg/webhook/{radarr.go => arr.go} (53%) create mode 100644 packages/arrtrix/pkg/webhook/arr_test.go delete mode 100644 packages/arrtrix/pkg/webhook/radarr_test.go diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e62b828 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,6 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 diff --git a/.gitattributes b/.gitattributes index 780e15a..6313b56 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1 @@ -* text=auto -core.autocrlf=false -core.eol=lf -core.filemode=false +* text=auto eol=lf diff --git a/modules/nixos/services/communication/matrix/default.nix b/modules/nixos/services/communication/matrix/default.nix index c9c11f1..607fa72 100644 --- a/modules/nixos/services/communication/matrix/default.nix +++ b/modules/nixos/services/communication/matrix/default.nix @@ -18,7 +18,7 @@ keyFile = "/var/lib/element-call/key"; mkMautrix = bridge: i: conf: { - ${bridge} = + ${bridge} = mkMerge [ { enable = true; registerToSynapse = true; @@ -43,7 +43,8 @@ }; }; } - // conf; + conf + ]; }; in { options.${namespace}.services.communication.matrix = { @@ -110,7 +111,13 @@ in { (mkMautrix "mautrix-signal" 1 {}) (mkMautrix "mautrix-telegram" 2 {}) (mkMautrix "mautrix-whatsapp" 3 {}) - (mkMautrix "arrtrix" 4 {}) + (mkMautrix "arrtrix" 4 { + settings.network.webhooks.radarr = { + enabled = true; + path = "/_arrtrix/webhooks/radarr"; + secret = ""; + }; + }) { matrix-synapse = { enable = true; diff --git a/modules/nixos/temp/services/arrtrix/default.nix b/modules/nixos/temp/services/arrtrix/default.nix index 67ff0b9..618de39 100644 --- a/modules/nixos/temp/services/arrtrix/default.nix +++ b/modules/nixos/temp/services/arrtrix/default.nix @@ -15,11 +15,6 @@ settingsFormat = pkgs.formats.json {}; defaultConfig = { - network.webhooks.radarr = { - enabled = false; - path = "/_arrtrix/webhooks/radarr"; - secret = ""; - }; bridge = { command_prefix = "!arr"; relay.enabled = true; diff --git a/packages/arrtrix/pkg/config/config_test.go b/packages/arrtrix/pkg/config/config_test.go index dc08292..84b09df 100644 --- a/packages/arrtrix/pkg/config/config_test.go +++ b/packages/arrtrix/pkg/config/config_test.go @@ -120,3 +120,40 @@ encryption: t.Fatalf("expected hidden double puppet secrets to stay internal-only") } } + +func TestLoadIgnoresLegacyWebhookSettings(t *testing.T) { + cfg, err := Load([]byte(` +network: + webhooks: + radarr: + enabled: true + path: /_arrtrix/webhooks/radarr + secret: legacy-secret +bridge: + command_prefix: "!arr" +homeserver: + address: http://127.0.0.1:8008 + domain: test.local +appservice: + id: arrtrix + bot: + username: arrtrixbot + displayname: Arrtrix Bot + username_template: arrtrix_{{.}} +database: + type: sqlite3-fk-wal + uri: file:arrtrix.db?_txlock=immediate +logging: + min_level: info + writers: + - type: stdout + format: pretty-colored +`)) + if err != nil { + t.Fatalf("Load returned error: %v", err) + } + + if cfg == nil { + t.Fatal("expected config to load") + } +} diff --git a/packages/arrtrix/pkg/connector/config.go b/packages/arrtrix/pkg/connector/config.go index 3702cee..2cdec34 100644 --- a/packages/arrtrix/pkg/connector/config.go +++ b/packages/arrtrix/pkg/connector/config.go @@ -14,40 +14,23 @@ import ( //go:embed example-config.yaml var ExampleConfig string -type Config struct { - Webhooks WebhooksConfig `yaml:"webhooks"` -} - -type WebhooksConfig struct { - Radarr webhook.RadarrConfig `yaml:"radarr"` -} - -func (c *Config) applyDefaults() { - c.Webhooks.Radarr.ApplyDefaults() -} - -func (c *Config) Validate() error { - return c.Webhooks.Radarr.Validate() -} +type Config struct{} func upgradeConfig(helper up.Helper) {} func (s *ArrtrixConnector) GetConfig() (string, any, up.Upgrader) { - s.Config.applyDefaults() return ExampleConfig, &s.Config, up.SimpleUpgrader(upgradeConfig) } func (s *ArrtrixConnector) ValidateConfig() error { - s.Config.applyDefaults() - return s.Config.Validate() + return nil } func (s *ArrtrixConnector) MountRoutes(router *http.ServeMux) error { - s.Config.applyDefaults() if s.Bridge == nil { return fmt.Errorf("bridge is not initialized") } - return webhook.MountRadarr(router, s.Bridge, s.Config.Webhooks.Radarr) + return webhook.MountArr(router, s.Bridge) } var _ bridgev2.ConfigValidatingNetwork = (*ArrtrixConnector)(nil) diff --git a/packages/arrtrix/pkg/connector/config_test.go b/packages/arrtrix/pkg/connector/config_test.go deleted file mode 100644 index 5199308..0000000 --- a/packages/arrtrix/pkg/connector/config_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package connector - -import "testing" - -func TestConfigDefaultsApplyRadarrWebhookPath(t *testing.T) { - var cfg Config - - cfg.applyDefaults() - - if cfg.Webhooks.Radarr.Path == "" { - t.Fatal("expected radarr webhook path default to be set") - } -} - -func TestConfigValidateRejectsEnabledWebhookWithoutSecret(t *testing.T) { - cfg := Config{} - cfg.Webhooks.Radarr.Enabled = true - cfg.applyDefaults() - - if err := cfg.Validate(); err == nil { - t.Fatal("expected missing secret to fail validation") - } -} diff --git a/packages/arrtrix/pkg/connector/example-config.yaml b/packages/arrtrix/pkg/connector/example-config.yaml index 5fa52c6..9c11ddf 100644 --- a/packages/arrtrix/pkg/connector/example-config.yaml +++ b/packages/arrtrix/pkg/connector/example-config.yaml @@ -1,10 +1,4 @@ -# Arrtrix-specific runtime options. +# No network-specific config is required yet. # -webhooks: - radarr: - enabled: false - path: /_arrtrix/webhooks/radarr - secret: "" - # The first implementation delivers notifications to the only configured - # management room. If more than one management room exists, the webhook is - # rejected until routing is configured more explicitly. +# Arr-stack webhooks are exposed automatically on the fixed built-in path: +# POST /_arrtrix/webhook diff --git a/packages/arrtrix/pkg/webhook/radarr.go b/packages/arrtrix/pkg/webhook/arr.go similarity index 53% rename from packages/arrtrix/pkg/webhook/radarr.go rename to packages/arrtrix/pkg/webhook/arr.go index 6f74342..42e350c 100644 --- a/packages/arrtrix/pkg/webhook/radarr.go +++ b/packages/arrtrix/pkg/webhook/arr.go @@ -14,30 +14,21 @@ import ( "maunium.net/go/mautrix/id" ) -const ( - defaultRadarrWebhookPath = "/_arrtrix/webhooks/radarr" - radarrSecretHeader = "X-Arrtrix-Webhook-Secret" -) +const ArrWebhookPath = "/_arrtrix/webhook" var ( ErrNoManagementRoom = errors.New("no management room configured") ErrAmbiguousManagementRoom = errors.New("multiple management rooms configured") ) -type RadarrConfig struct { - Enabled bool `yaml:"enabled"` - Path string `yaml:"path"` - Secret string `yaml:"secret"` +type payload struct { + EventType string `json:"eventType"` + Movie *movie `json:"movie"` + MovieFile *movieFile `json:"movieFile"` + IsUpgrade bool `json:"isUpgrade"` } -type radarrPayload struct { - EventType string `json:"eventType"` - Movie *radarrMovie `json:"movie"` - MovieFile *radarrMovieFile `json:"movieFile"` - IsUpgrade bool `json:"isUpgrade"` -} - -type radarrMovie struct { +type movie struct { Title string `json:"title"` Year int `json:"year"` ImdbID string `json:"imdbId"` @@ -45,7 +36,7 @@ type radarrMovie struct { Path string `json:"path"` } -type radarrMovieFile struct { +type movieFile struct { Quality string `json:"quality"` RelativePath string `json:"relativePath"` SceneName string `json:"sceneName"` @@ -60,62 +51,30 @@ type noticeSender interface { SendNotice(context.Context, id.RoomID, string) error } -type RadarrHandler struct { - config RadarrConfig +type ArrHandler struct { resolver roomResolver sender noticeSender } -func (c *RadarrConfig) ApplyDefaults() { - if c.Path == "" { - c.Path = defaultRadarrWebhookPath +func MountArr(router *http.ServeMux, bridge *bridgev2.Bridge) error { + if bridge == nil { + return fmt.Errorf("bridge is not initialized") } -} - -func (c *RadarrConfig) Validate() error { - c.ApplyDefaults() - if !c.Enabled { - return nil - } - if !strings.HasPrefix(c.Path, "/") { - return fmt.Errorf("network.webhooks.radarr.path must start with /") - } - if strings.TrimSpace(c.Secret) == "" { - return fmt.Errorf("network.webhooks.radarr.secret must be set when the webhook is enabled") - } - return nil -} - -func MountRadarr(router *http.ServeMux, bridge *bridgev2.Bridge, cfg RadarrConfig) error { - cfg.ApplyDefaults() - if !cfg.Enabled { - return nil - } - if err := cfg.Validate(); err != nil { - return err - } - - handler := &RadarrHandler{ - config: cfg, + handler := &ArrHandler{ resolver: bridgeRoomResolver{bridge: bridge}, sender: bridgeNoticeSender{bridge: bridge}, } - router.Handle(fmt.Sprintf("POST %s", cfg.Path), handler) + router.Handle(fmt.Sprintf("POST %s", ArrWebhookPath), handler) return nil } -func (h *RadarrHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if !authorized(r, h.config.Secret) { - http.Error(w, "invalid webhook secret", http.StatusUnauthorized) - return - } - - var payload radarrPayload - if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { +func (h *ArrHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + var body payload + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { http.Error(w, "invalid webhook payload", http.StatusBadRequest) return } - if strings.TrimSpace(payload.EventType) == "" { + if strings.TrimSpace(body.EventType) == "" { http.Error(w, "missing eventType", http.StatusBadRequest) return } @@ -130,7 +89,7 @@ func (h *RadarrHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - if err = h.sender.SendNotice(r.Context(), roomID, renderRadarrNotice(payload)); err != nil { + if err = h.sender.SendNotice(r.Context(), roomID, renderNotice(body)); err != nil { http.Error(w, "failed to deliver webhook", http.StatusBadGateway) return } @@ -164,6 +123,7 @@ func (r bridgeRoomResolver) ResolveManagementRoom(ctx context.Context) (id.RoomI if err = rows.Err(); err != nil { return "", fmt.Errorf("failed to iterate management rooms: %w", err) } + switch len(owners) { case 0: return "", ErrNoManagementRoom @@ -187,43 +147,30 @@ func (s bridgeNoticeSender) SendNotice(ctx context.Context, roomID id.RoomID, ma return err } -func authorized(r *http.Request, secret string) bool { - if secret == "" { - return true - } - if r.Header.Get(radarrSecretHeader) == secret { - return true - } - if bearer := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer "); bearer == secret && bearer != r.Header.Get("Authorization") { - return true - } - return r.URL.Query().Get("secret") == secret -} - -func renderRadarrNotice(payload radarrPayload) string { - title := "Radarr" - if payload.Movie != nil { - title = payload.Movie.Title - if payload.Movie.Year != 0 { - title = fmt.Sprintf("%s (%d)", title, payload.Movie.Year) +func renderNotice(body payload) string { + title := "Arr" + if body.Movie != nil { + title = body.Movie.Title + if body.Movie.Year != 0 { + title = fmt.Sprintf("%s (%d)", title, body.Movie.Year) } } - lines := []string{fmt.Sprintf("**Radarr %s**", payload.EventType)} - if title != "Radarr" { + lines := []string{fmt.Sprintf("**Arr %s**", body.EventType)} + if title != "Arr" { lines = append(lines, fmt.Sprintf("Movie: %s", title)) } - if payload.MovieFile != nil && payload.MovieFile.Quality != "" { - lines = append(lines, fmt.Sprintf("Quality: %s", payload.MovieFile.Quality)) + if body.MovieFile != nil && body.MovieFile.Quality != "" { + lines = append(lines, fmt.Sprintf("Quality: %s", body.MovieFile.Quality)) } - if payload.MovieFile != nil && payload.MovieFile.RelativePath != "" { - lines = append(lines, fmt.Sprintf("File: `%s`", payload.MovieFile.RelativePath)) + if body.MovieFile != nil && body.MovieFile.RelativePath != "" { + lines = append(lines, fmt.Sprintf("File: `%s`", body.MovieFile.RelativePath)) } - if payload.EventType == "Download" { - lines = append(lines, fmt.Sprintf("Upgrade: %t", payload.IsUpgrade)) + if body.EventType == "Download" { + lines = append(lines, fmt.Sprintf("Upgrade: %t", body.IsUpgrade)) } - if payload.Movie != nil && payload.Movie.ImdbID != "" { - lines = append(lines, fmt.Sprintf("IMDb: `%s`", payload.Movie.ImdbID)) + if body.Movie != nil && body.Movie.ImdbID != "" { + lines = append(lines, fmt.Sprintf("IMDb: `%s`", body.Movie.ImdbID)) } return strings.Join(lines, "\n") } @@ -238,4 +185,4 @@ func convertUserIDs(users []id.UserID) []string { var _ roomResolver = bridgeRoomResolver{} var _ noticeSender = bridgeNoticeSender{} -var _ http.Handler = (*RadarrHandler)(nil) +var _ http.Handler = (*ArrHandler)(nil) diff --git a/packages/arrtrix/pkg/webhook/arr_test.go b/packages/arrtrix/pkg/webhook/arr_test.go new file mode 100644 index 0000000..b7ac511 --- /dev/null +++ b/packages/arrtrix/pkg/webhook/arr_test.go @@ -0,0 +1,114 @@ +package webhook + +import ( + "context" + "errors" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "maunium.net/go/mautrix/id" +) + +type stubRoomResolver struct { + roomID id.RoomID + err error +} + +func (s stubRoomResolver) ResolveManagementRoom(context.Context) (id.RoomID, error) { + return s.roomID, s.err +} + +type stubNoticeSender struct { + roomID id.RoomID + message string + err error +} + +func (s *stubNoticeSender) SendNotice(_ context.Context, roomID id.RoomID, message string) error { + s.roomID = roomID + s.message = message + return s.err +} + +func TestMountArrRequiresBridge(t *testing.T) { + router := http.NewServeMux() + if err := MountArr(router, nil); err == nil { + t.Fatal("expected nil bridge to fail") + } +} + +func TestArrHandlerDeliversNotice(t *testing.T) { + sender := &stubNoticeSender{} + handler := &ArrHandler{ + resolver: stubRoomResolver{roomID: "!room:test"}, + sender: sender, + } + + req := httptest.NewRequest(http.MethodPost, ArrWebhookPath, strings.NewReader(`{"eventType":"Download","movie":{"title":"Dune","year":2021,"imdbId":"tt1160419"},"movieFile":{"quality":"1080p","relativePath":"Dune (2021)/Dune.mkv"},"isUpgrade":false}`)) + rec := httptest.NewRecorder() + handler.ServeHTTP(rec, req) + + if rec.Code != http.StatusAccepted { + t.Fatalf("expected accepted status, got %d", rec.Code) + } + if sender.roomID != "!room:test" { + t.Fatalf("expected notice sent to management room, got %q", sender.roomID) + } + if !strings.Contains(sender.message, "**Arr Download**") || !strings.Contains(sender.message, "Dune (2021)") { + t.Fatalf("unexpected message: %s", sender.message) + } +} + +func TestArrHandlerReportsAmbiguousManagementRoom(t *testing.T) { + handler := &ArrHandler{ + resolver: stubRoomResolver{err: ErrAmbiguousManagementRoom}, + sender: &stubNoticeSender{}, + } + + req := httptest.NewRequest(http.MethodPost, ArrWebhookPath, strings.NewReader(`{"eventType":"Test"}`)) + rec := httptest.NewRecorder() + handler.ServeHTTP(rec, req) + + if rec.Code != http.StatusConflict { + t.Fatalf("expected conflict status, got %d", rec.Code) + } +} + +func TestRenderNoticeForTestEvent(t *testing.T) { + msg := renderNotice(payload{EventType: "Test"}) + if strings.TrimSpace(msg) != "**Arr Test**" { + t.Fatalf("unexpected test-event message: %q", msg) + } +} + +func TestArrHandlerReturnsBadGatewayOnSendFailure(t *testing.T) { + handler := &ArrHandler{ + resolver: stubRoomResolver{roomID: "!room:test"}, + sender: &stubNoticeSender{err: errors.New("send failed")}, + } + + req := httptest.NewRequest(http.MethodPost, ArrWebhookPath, strings.NewReader(`{"eventType":"Test"}`)) + rec := httptest.NewRecorder() + handler.ServeHTTP(rec, req) + + if rec.Code != http.StatusBadGateway { + t.Fatalf("expected bad gateway status, got %d", rec.Code) + } +} + +func TestArrHandlerRejectsMissingEventType(t *testing.T) { + handler := &ArrHandler{ + resolver: stubRoomResolver{roomID: "!room:test"}, + sender: &stubNoticeSender{}, + } + + req := httptest.NewRequest(http.MethodPost, ArrWebhookPath, strings.NewReader(`{"movie":{"title":"Dune"}}`)) + rec := httptest.NewRecorder() + handler.ServeHTTP(rec, req) + + if rec.Code != http.StatusBadRequest { + t.Fatalf("expected bad request status, got %d", rec.Code) + } +} diff --git a/packages/arrtrix/pkg/webhook/radarr_test.go b/packages/arrtrix/pkg/webhook/radarr_test.go deleted file mode 100644 index d4fc962..0000000 --- a/packages/arrtrix/pkg/webhook/radarr_test.go +++ /dev/null @@ -1,131 +0,0 @@ -package webhook - -import ( - "context" - "errors" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "maunium.net/go/mautrix/id" -) - -type stubRoomResolver struct { - roomID id.RoomID - err error -} - -func (s stubRoomResolver) ResolveManagementRoom(context.Context) (id.RoomID, error) { - return s.roomID, s.err -} - -type stubNoticeSender struct { - roomID id.RoomID - message string - err error -} - -func (s *stubNoticeSender) SendNotice(_ context.Context, roomID id.RoomID, message string) error { - s.roomID = roomID - s.message = message - return s.err -} - -func TestRadarrConfigDefaultsAndValidation(t *testing.T) { - cfg := RadarrConfig{Enabled: true, Secret: "secret"} - cfg.ApplyDefaults() - if cfg.Path != defaultRadarrWebhookPath { - t.Fatalf("expected default path %q, got %q", defaultRadarrWebhookPath, cfg.Path) - } - if err := cfg.Validate(); err != nil { - t.Fatalf("expected config to validate, got %v", err) - } -} - -func TestRadarrConfigRequiresSecretWhenEnabled(t *testing.T) { - cfg := RadarrConfig{Enabled: true} - if err := cfg.Validate(); err == nil { - t.Fatal("expected missing secret to fail validation") - } -} - -func TestRadarrHandlerRejectsUnauthorizedRequests(t *testing.T) { - handler := &RadarrHandler{ - config: RadarrConfig{Enabled: true, Secret: "secret"}, - resolver: stubRoomResolver{roomID: "!room:test"}, - sender: &stubNoticeSender{}, - } - - req := httptest.NewRequest(http.MethodPost, defaultRadarrWebhookPath, strings.NewReader(`{"eventType":"Test"}`)) - rec := httptest.NewRecorder() - handler.ServeHTTP(rec, req) - - if rec.Code != http.StatusUnauthorized { - t.Fatalf("expected unauthorized status, got %d", rec.Code) - } -} - -func TestRadarrHandlerDeliversNotice(t *testing.T) { - sender := &stubNoticeSender{} - handler := &RadarrHandler{ - config: RadarrConfig{Enabled: true, Secret: "secret"}, - resolver: stubRoomResolver{roomID: "!room:test"}, - sender: sender, - } - - req := httptest.NewRequest(http.MethodPost, defaultRadarrWebhookPath+"?secret=secret", strings.NewReader(`{"eventType":"Download","movie":{"title":"Dune","year":2021,"imdbId":"tt1160419"},"movieFile":{"quality":"1080p","relativePath":"Dune (2021)/Dune.mkv"},"isUpgrade":false}`)) - rec := httptest.NewRecorder() - handler.ServeHTTP(rec, req) - - if rec.Code != http.StatusAccepted { - t.Fatalf("expected accepted status, got %d", rec.Code) - } - if sender.roomID != "!room:test" { - t.Fatalf("expected notice sent to management room, got %q", sender.roomID) - } - if !strings.Contains(sender.message, "**Radarr Download**") || !strings.Contains(sender.message, "Dune (2021)") { - t.Fatalf("unexpected message: %s", sender.message) - } -} - -func TestRadarrHandlerReportsAmbiguousManagementRoom(t *testing.T) { - handler := &RadarrHandler{ - config: RadarrConfig{Enabled: true, Secret: "secret"}, - resolver: stubRoomResolver{err: ErrAmbiguousManagementRoom}, - sender: &stubNoticeSender{}, - } - - req := httptest.NewRequest(http.MethodPost, defaultRadarrWebhookPath, strings.NewReader(`{"eventType":"Test"}`)) - req.Header.Set(radarrSecretHeader, "secret") - rec := httptest.NewRecorder() - handler.ServeHTTP(rec, req) - - if rec.Code != http.StatusConflict { - t.Fatalf("expected conflict status, got %d", rec.Code) - } -} - -func TestRenderRadarrNoticeForTestEvent(t *testing.T) { - msg := renderRadarrNotice(radarrPayload{EventType: "Test"}) - if strings.TrimSpace(msg) != "**Radarr Test**" { - t.Fatalf("unexpected test-event message: %q", msg) - } -} - -func TestRadarrHandlerReturnsBadGatewayOnSendFailure(t *testing.T) { - handler := &RadarrHandler{ - config: RadarrConfig{Enabled: true, Secret: "secret"}, - resolver: stubRoomResolver{roomID: "!room:test"}, - sender: &stubNoticeSender{err: errors.New("send failed")}, - } - - req := httptest.NewRequest(http.MethodPost, defaultRadarrWebhookPath, strings.NewReader(`{"eventType":"Test"}`)) - req.Header.Set(radarrSecretHeader, "secret") - rec := httptest.NewRecorder() - handler.ServeHTTP(rec, req) - - if rec.Code != http.StatusBadGateway { - t.Fatalf("expected bad gateway status, got %d", rec.Code) - } -} From 81f34676c42df578de285b7a2fddf5d7a2904751 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Thu, 16 Apr 2026 10:13:51 +0200 Subject: [PATCH 103/108] Add OpenTelemetry observability to Arrtrix - Add OTLP/gRPC observability config and resource attributes - Instrument webhook and onboarding handlers with tracing and metrics - Add OpenTelemetry dependencies to go.mod/go.sum - Update NixOS modules to configure observability settings --- .../services/communication/matrix/default.nix | 7 +- .../nixos/services/media/servarr/default.nix | 12 +++ .../nixos/temp/services/arrtrix/default.nix | 5 ++ packages/arrtrix/go.mod | 37 ++++++--- packages/arrtrix/go.sum | 67 ++++++++++++++-- packages/arrtrix/pkg/config/config.go | 4 + packages/arrtrix/pkg/matrixcmd/processor.go | 23 +++++- packages/arrtrix/pkg/onboarding/welcome.go | 34 ++++++++ packages/arrtrix/pkg/runtime/example.go | 7 ++ packages/arrtrix/pkg/runtime/main.go | 63 +++++++++++++++ packages/arrtrix/pkg/webhook/arr.go | 77 +++++++++++++++++-- 11 files changed, 307 insertions(+), 29 deletions(-) diff --git a/modules/nixos/services/communication/matrix/default.nix b/modules/nixos/services/communication/matrix/default.nix index 607fa72..cd5aff2 100644 --- a/modules/nixos/services/communication/matrix/default.nix +++ b/modules/nixos/services/communication/matrix/default.nix @@ -112,10 +112,9 @@ in { (mkMautrix "mautrix-telegram" 2 {}) (mkMautrix "mautrix-whatsapp" 3 {}) (mkMautrix "arrtrix" 4 { - settings.network.webhooks.radarr = { - enabled = true; - path = "/_arrtrix/webhooks/radarr"; - secret = ""; + settings.observability = { + otlp_grpc_endpoint = "http://[::1]:1000"; + service_name = "arrtrix"; }; }) { diff --git a/modules/nixos/services/media/servarr/default.nix b/modules/nixos/services/media/servarr/default.nix index ae0e3b0..23255c0 100644 --- a/modules/nixos/services/media/servarr/default.nix +++ b/modules/nixos/services/media/servarr/default.nix @@ -212,6 +212,18 @@ in { resource = { + "${service}_notification_webhook" = mkIf (lib.elem service ["radarr" "sonarr" "whisparr" "lidarr" "readarr"]) { + "arrtrix" = + { + method = 1; # HTTP METHOD 1=POST, 2=PUT + name = "Arrtrix"; + url = "http://[::1]${config'.services.arrtrix.settings.appservice.port}"; + } + // (lib.optionalAttrs (lib.elem service ["radarr" "whisparr"]) { + onMovieDelete = true; + }); + }; + "${service}_root_folder" = mkIf (lib.elem service ["radarr" "sonarr" "whisparr"]) ( rootFolders |> lib.imap (i: f: lib.nameValuePair "local${toString i}" {path = f;}) diff --git a/modules/nixos/temp/services/arrtrix/default.nix b/modules/nixos/temp/services/arrtrix/default.nix index 618de39..b8c7457 100644 --- a/modules/nixos/temp/services/arrtrix/default.nix +++ b/modules/nixos/temp/services/arrtrix/default.nix @@ -48,6 +48,11 @@ time_format = " "; }; }; + observability = { + otlp_grpc_endpoint = ""; + service_name = "arrtrix"; + resource_attributes = {}; + }; }; in { options.services.arrtrix = { diff --git a/packages/arrtrix/go.mod b/packages/arrtrix/go.mod index eed27b5..81a6c93 100644 --- a/packages/arrtrix/go.mod +++ b/packages/arrtrix/go.mod @@ -3,41 +3,58 @@ module sneeuwvlok/packages/arrtrix go 1.25.0 require ( + github.com/rs/zerolog v1.34.0 go.mau.fi/util v0.9.7 + go.mau.fi/zeroconfig v0.2.0 + go.opentelemetry.io/otel v1.43.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 + go.opentelemetry.io/otel/log v0.19.0 + go.opentelemetry.io/otel/metric v1.43.0 + go.opentelemetry.io/otel/sdk v1.43.0 + go.opentelemetry.io/otel/sdk/log v0.19.0 + go.opentelemetry.io/otel/sdk/metric v1.43.0 + go.opentelemetry.io/otel/trace v1.43.0 + gopkg.in/yaml.v3 v3.0.1 + maunium.net/go/mauflag v1.0.0 maunium.net/go/mautrix v0.26.4 ) -require ( - github.com/kr/pretty v0.3.1 // indirect - github.com/rogpeppe/go-internal v1.10.0 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect -) - require ( filippo.io/edwards25519 v1.2.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coder/websocket v1.8.14 // indirect github.com/coreos/go-systemd/v22 v22.6.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect github.com/lib/pq v1.11.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-sqlite3 v1.14.34 // indirect github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6 // indirect github.com/rs/xid v1.6.0 // indirect - github.com/rs/zerolog v1.34.0 // indirect github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.2.0 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect github.com/yuin/goldmark v1.7.16 // indirect - go.mau.fi/zeroconfig v0.2.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect + go.opentelemetry.io/proto/otlp v1.10.0 // indirect golang.org/x/crypto v0.49.0 // indirect golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect golang.org/x/net v0.52.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.42.0 // indirect golang.org/x/text v0.35.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect + google.golang.org/grpc v1.80.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - maunium.net/go/mauflag v1.0.0 // indirect ) diff --git a/packages/arrtrix/go.sum b/packages/arrtrix/go.sum index d8e9404..8d8f5ab 100644 --- a/packages/arrtrix/go.sum +++ b/packages/arrtrix/go.sum @@ -2,20 +2,33 @@ filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs= @@ -31,13 +44,11 @@ github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6 h1:rh2lKw/P/EqHa724vYH2+VVQ1YnW4u6EOXl0PMAovZE= github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= @@ -63,6 +74,36 @@ go.mau.fi/util v0.9.7 h1:AWGNbJfz1zRcQOKeOEYhKUG2fT+/26Gy6kyqcH8tnBg= go.mau.fi/util v0.9.7/go.mod h1:5T2f3ZWZFAGgmFwg3dGw7YK6kIsb9lryDzvynoR98pE= go.mau.fi/zeroconfig v0.2.0 h1:e/OGEERqVRRKlgaro7E6bh8xXiKFSXB3eNNIud7FUjU= go.mau.fi/zeroconfig v0.2.0/go.mod h1:J0Vn0prHNOm493oZoQ84kq83ZaNCYZnq+noI1b1eN8w= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 h1:Dn8rkudDzY6KV9dr/D/bTUuWgqDf9xe0rr4G2elrn0Y= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0/go.mod h1:gMk9F0xDgyN9M/3Ed5Y1wKcx/9mlU91NXY2SNq7RQuU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 h1:8UQVDcZxOJLtX6gxtDt3vY2WTgvZqMQRzjsqiIHQdkc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0/go.mod h1:2lmweYCiHYpEjQ/lSJBYhj9jP1zvCvQW4BqL9dnT7FQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 h1:RAE+JPfvEmvy+0LzyUA25/SGawPwIUbZ6u0Wug54sLc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0/go.mod h1:AGmbycVGEsRx9mXMZ75CsOyhSP6MFIcj/6dnG+vhVjk= +go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4= +go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko= +go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg= +go.opentelemetry.io/otel/sdk/log/logtest v0.19.0 h1:BEbF7ZBB6qQloV/Ub1+3NQoOUnVtcGkU3XX4Ws3GQfk= +go.opentelemetry.io/otel/sdk/log/logtest v0.19.0/go.mod h1:Lua81/3yM0wOmoHTokLj9y9ADeA02v1naRrVrkAZuKk= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= +go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA= @@ -78,6 +119,16 @@ golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= +google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= +google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/packages/arrtrix/pkg/config/config.go b/packages/arrtrix/pkg/config/config.go index c3b11b8..ff97e98 100644 --- a/packages/arrtrix/pkg/config/config.go +++ b/packages/arrtrix/pkg/config/config.go @@ -6,6 +6,8 @@ import ( "gopkg.in/yaml.v3" "maunium.net/go/mautrix/bridgev2/bridgeconfig" + + "sneeuwvlok/packages/arrtrix/pkg/observability" ) type Config struct { @@ -17,6 +19,7 @@ type Config struct { AppService bridgeconfig.AppserviceConfig `yaml:"appservice"` Logging zeroconfig.Config `yaml:"logging"` + Observability observability.Config `yaml:"observability"` EnvConfigPrefix string `yaml:"env_config_prefix"` ManagementTexts bridgeconfig.ManagementRoomTexts `yaml:"management_room_texts"` } @@ -34,6 +37,7 @@ func (c *Config) applyDefaults() { if c.Homeserver.Software == "" { c.Homeserver.Software = bridgeconfig.SoftwareStandard } + c.Observability.ApplyDefaults() } func (c *Config) Compile() bridgeconfig.Config { diff --git a/packages/arrtrix/pkg/matrixcmd/processor.go b/packages/arrtrix/pkg/matrixcmd/processor.go index 1dabfd6..a4f15df 100644 --- a/packages/arrtrix/pkg/matrixcmd/processor.go +++ b/packages/arrtrix/pkg/matrixcmd/processor.go @@ -8,6 +8,8 @@ import ( "strings" "github.com/rs/zerolog" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2/bridgeconfig" @@ -15,6 +17,8 @@ import ( "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/format" "maunium.net/go/mautrix/id" + + "sneeuwvlok/packages/arrtrix/pkg/observability" ) type Handler interface { @@ -110,6 +114,9 @@ func (p *Processor) Handlers() []Handler { } func (p *Processor) Handle(ctx context.Context, roomID id.RoomID, eventID id.EventID, user *bridgev2.User, message string, replyTo id.EventID) { + ctx, span := observability.StartSpan(ctx, "arrtrix.matrix.command") + defer span.End() + ms := &bridgev2.MessageStatus{ Step: status.MsgStepCommand, Status: event.MessageStatusSuccess, @@ -117,6 +124,8 @@ func (p *Processor) Handle(ctx context.Context, roomID id.RoomID, eventID id.Eve logCopy := zerolog.Ctx(ctx).With().Logger() log := &logCopy + outcome := "success" + commandName := "unknown-command" defer func() { statusInfo := &bridgev2.MessageStatusEventInfo{ @@ -131,16 +140,21 @@ func (p *Processor) Handle(ctx context.Context, roomID id.RoomID, eventID id.Eve if err, ok := recovered.(error); ok { logEvt = logEvt.Err(err) ms.InternalError = err + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) } else { logEvt = logEvt.Any(zerolog.ErrorFieldName, recovered) ms.InternalError = fmt.Errorf("%v", recovered) + span.SetStatus(codes.Error, "panic") } logEvt.Msg("Panic in arrtrix Matrix command handler") ms.Status = event.MessageStatusFail ms.IsCertain = true ms.ErrorAsMessage = true + outcome = "panic" } + observability.RecordCommand(ctx, commandName, outcome) p.bridge.Matrix.SendMessageStatus(ctx, ms, statusInfo) }() @@ -149,10 +163,14 @@ func (p *Processor) Handle(ctx context.Context, roomID id.RoomID, eventID id.Eve args = []string{"unknown-command"} } - commandName := strings.ToLower(args[0]) + commandName = strings.ToLower(args[0]) if actual, ok := p.alias[commandName]; ok { commandName = actual } + span.SetAttributes( + attribute.String("arrtrix.matrix.command.name", commandName), + attribute.String("matrix.room_id", roomID.String()), + ) portal, err := p.bridge.GetPortalByMXID(ctx, roomID) if err != nil { @@ -179,6 +197,8 @@ func (p *Processor) Handle(ctx context.Context, roomID id.RoomID, eventID id.Eve handler, ok := p.command[commandName] if !ok { log.Debug().Str("mx_command", commandName).Msg("Received unknown Matrix room command") + span.SetStatus(codes.Error, "unknown command") + outcome = "unknown" commandCtx.Reply("Unknown command, use the `help` command for help.") return } @@ -188,6 +208,7 @@ func (p *Processor) Handle(ctx context.Context, roomID id.RoomID, eventID id.Eve }) log.Debug().Msg("Received Matrix room command") handler.Run(commandCtx) + span.SetStatus(codes.Ok, "") } func (c *Context) Reply(message string, args ...any) { diff --git a/packages/arrtrix/pkg/onboarding/welcome.go b/packages/arrtrix/pkg/onboarding/welcome.go index 14860c1..e96ea7a 100644 --- a/packages/arrtrix/pkg/onboarding/welcome.go +++ b/packages/arrtrix/pkg/onboarding/welcome.go @@ -6,12 +6,16 @@ import ( "strings" "github.com/rs/zerolog" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2/bridgeconfig" "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/format" "maunium.net/go/mautrix/id" + + "sneeuwvlok/packages/arrtrix/pkg/observability" ) const handledInviteEventType = "com.arrtrix.handled_invite" @@ -23,27 +27,49 @@ func HandleBotInvite(ctx context.Context, bridge *bridgev2.Bridge, texts bridgec return } + ctx, span := observability.StartSpan(ctx, "arrtrix.matrix.invite") + defer span.End() + span.SetAttributes( + attribute.String("matrix.room_id", evt.RoomID.String()), + attribute.String("matrix.sender", evt.Sender.String()), + ) + outcome := "ignored" + defer observability.RecordInvite(ctx, outcome) + log := zerolog.Ctx(ctx) sender, err := bridge.GetUserByMXID(ctx, evt.Sender) if err != nil { + outcome = "user_lookup_failed" + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Err(err).Msg("Failed to load sender for bot invite") return } if !sender.Permissions.Commands { + outcome = "permission_denied" + span.SetStatus(codes.Error, "sender lacks command permission") return } if err = bridge.Bot.EnsureJoined(ctx, evt.RoomID); err != nil { + outcome = "join_failed" + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Err(err).Msg("Failed to accept invite to room") return } members, err := bridge.Matrix.GetMembers(ctx, evt.RoomID) if err != nil { + outcome = "member_lookup_failed" + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Err(err).Msg("Failed to get members of room after accepting invite") return } if len(members) != 2 { + outcome = "non_management_room" + span.SetStatus(codes.Error, "invite room is not a direct management room") return } @@ -51,6 +77,9 @@ func HandleBotInvite(ctx context.Context, bridge *bridgev2.Bridge, texts bridgec if assignedManagementRoom { sender.ManagementRoom = evt.RoomID if err = sender.Save(ctx); err != nil { + outcome = "management_room_save_failed" + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Err(err).Msg("Failed to update user's management room in database") return } @@ -59,10 +88,15 @@ func HandleBotInvite(ctx context.Context, bridge *bridgev2.Bridge, texts bridgec message := buildWelcomeMessage(bridge, texts, sender, assignedManagementRoom) content := format.RenderMarkdown(message, true, false) if _, err = bridge.Bot.SendMessage(ctx, evt.RoomID, event.EventMessage, &event.Content{Parsed: &content}, nil); err != nil { + outcome = "welcome_send_failed" + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) log.Err(err).Msg("Failed to send welcome message to room") return } + outcome = "welcomed" + span.SetStatus(codes.Ok, "") evt.Type = event.Type{Type: handledInviteEventType} } diff --git a/packages/arrtrix/pkg/runtime/example.go b/packages/arrtrix/pkg/runtime/example.go index 1cba7b6..c8d7ca4 100644 --- a/packages/arrtrix/pkg/runtime/example.go +++ b/packages/arrtrix/pkg/runtime/example.go @@ -56,6 +56,13 @@ logging: - type: stdout format: pretty-colored +observability: + # OTLP/gRPC endpoint for logs, traces, and metrics. + # Set to e.g. http://127.0.0.1:4317 to enable export. + otlp_grpc_endpoint: "" + service_name: arrtrix + resource_attributes: {} + management_room_texts: welcome: "" welcome_connected: "" diff --git a/packages/arrtrix/pkg/runtime/main.go b/packages/arrtrix/pkg/runtime/main.go index 42e1495..5352c54 100644 --- a/packages/arrtrix/pkg/runtime/main.go +++ b/packages/arrtrix/pkg/runtime/main.go @@ -18,6 +18,7 @@ import ( "go.mau.fi/util/exerrors" "go.mau.fi/util/exzerolog" "go.mau.fi/util/progver" + "go.opentelemetry.io/otel/codes" "gopkg.in/yaml.v3" flag "maunium.net/go/mauflag" "maunium.net/go/mautrix/appservice" @@ -31,6 +32,7 @@ import ( arrconfig "sneeuwvlok/packages/arrtrix/pkg/config" "sneeuwvlok/packages/arrtrix/pkg/matrixcmd" + "sneeuwvlok/packages/arrtrix/pkg/observability" "sneeuwvlok/packages/arrtrix/pkg/onboarding" ) @@ -62,6 +64,7 @@ type Main struct { Config *bridgeconfig.Config Matrix *matrix.Connector Bridge *bridgev2.Bridge + OTEL *observability.Runtime ConfigPath string RegistrationPath string @@ -251,6 +254,8 @@ func (m *Main) loadRegistrationTokens(cfg *bridgeconfig.Config) error { } func (m *Main) Init() { + start := time.Now() + ctx := context.Background() var err error m.Log, err = m.Config.Logging.Compile() if err != nil { @@ -265,6 +270,33 @@ func (m *Main) Init() { os.Exit(11) } + otelCtx, cancel := context.WithTimeout(ctx, 10*time.Second) + m.OTEL, err = observability.Setup(otelCtx, m.PublicConfig.Observability, m.Version) + cancel() + if err != nil { + m.Log.WithLevel(zerolog.FatalLevel).Err(err).Msg("Failed to initialize observability") + os.Exit(15) + } + if hook := m.OTEL.LoggerHook(); hook != nil { + logger := m.Log.Hook(hook) + m.Log = &logger + exzerolog.SetupDefaults(m.Log) + } + + ctx = m.Log.WithContext(context.Background()) + ctx, span := observability.StartSpan(ctx, "arrtrix.runtime.init") + defer func() { + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + observability.RecordStartupPhase(ctx, "init", "error", time.Since(start)) + return + } + span.SetStatus(codes.Ok, "") + observability.RecordStartupPhase(ctx, "init", "ok", time.Since(start)) + }() + defer span.End() + m.Log.Info(). Str("name", m.Name). Str("version", m.ver.FormattedVersion). @@ -306,17 +338,48 @@ func (m *Main) Init() { } func (m *Main) Start() { + start := time.Now() ctx := m.Log.WithContext(context.Background()) + ctx, span := observability.StartSpan(ctx, "arrtrix.runtime.start") + defer func() { + if r := recover(); r != nil { + span.SetStatus(codes.Error, "panic") + observability.RecordStartupPhase(ctx, "start", "panic", time.Since(start)) + span.End() + panic(r) + } + span.End() + }() if err := m.Bridge.Start(ctx); err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + observability.RecordStartupPhase(ctx, "start", "error", time.Since(start)) m.Log.Fatal().Err(err).Msg("Failed to start bridge") } + span.SetStatus(codes.Ok, "") + observability.RecordStartupPhase(ctx, "start", "ok", time.Since(start)) if m.PostStart != nil { m.PostStart() } } func (m *Main) Stop() { + start := time.Now() + ctx := m.Log.WithContext(context.Background()) + ctx, span := observability.StartSpan(ctx, "arrtrix.runtime.stop") + defer span.End() + m.Bridge.StopWithTimeout(5 * time.Second) + span.SetStatus(codes.Ok, "") + observability.RecordStartupPhase(ctx, "stop", "ok", time.Since(start)) + + if m.OTEL != nil { + shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + if err := m.OTEL.Shutdown(shutdownCtx); err != nil && m.Log != nil { + m.Log.Error().Err(err).Msg("Failed to shut down observability") + } + } } func (m *Main) WaitForInterrupt() int { diff --git a/packages/arrtrix/pkg/webhook/arr.go b/packages/arrtrix/pkg/webhook/arr.go index 42e350c..eb7540c 100644 --- a/packages/arrtrix/pkg/webhook/arr.go +++ b/packages/arrtrix/pkg/webhook/arr.go @@ -7,11 +7,17 @@ import ( "fmt" "net/http" "strings" + "time" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/format" "maunium.net/go/mautrix/id" + + "sneeuwvlok/packages/arrtrix/pkg/observability" ) const ArrWebhookPath = "/_arrtrix/webhook" @@ -69,32 +75,65 @@ func MountArr(router *http.ServeMux, bridge *bridgev2.Bridge) error { } func (h *ArrHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + start := time.Now() + ctx, span := observability.StartSpan(r.Context(), "arrtrix.webhook.handle", trace.WithSpanKind(trace.SpanKindServer)) + defer span.End() + + statusCode := http.StatusAccepted + outcome := "ok" + eventType := "" + defer func() { + observability.RecordWebhook(ctx, eventType, outcome, statusCode, time.Since(start)) + }() + var body payload if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + statusCode = http.StatusBadRequest + outcome = "invalid_payload" + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) http.Error(w, "invalid webhook payload", http.StatusBadRequest) return } if strings.TrimSpace(body.EventType) == "" { + statusCode = http.StatusBadRequest + outcome = "missing_event_type" + span.SetStatus(codes.Error, "missing eventType") http.Error(w, "missing eventType", http.StatusBadRequest) return } + eventType = body.EventType + span.SetAttributes( + attribute.String("arrtrix.webhook.event_type", body.EventType), + attribute.String("http.method", r.Method), + attribute.String("http.route", ArrWebhookPath), + ) - roomID, err := h.resolver.ResolveManagementRoom(r.Context()) + roomID, err := h.resolver.ResolveManagementRoom(ctx) if err != nil { - status := http.StatusInternalServerError + statusCode = http.StatusInternalServerError + outcome = "resolve_failed" if errors.Is(err, ErrNoManagementRoom) || errors.Is(err, ErrAmbiguousManagementRoom) { - status = http.StatusConflict + statusCode = http.StatusConflict + outcome = "routing_conflict" } - http.Error(w, err.Error(), status) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + http.Error(w, err.Error(), statusCode) return } - if err = h.sender.SendNotice(r.Context(), roomID, renderNotice(body)); err != nil { + if err = h.sender.SendNotice(ctx, roomID, renderNotice(body)); err != nil { + statusCode = http.StatusBadGateway + outcome = "delivery_failed" + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) http.Error(w, "failed to deliver webhook", http.StatusBadGateway) return } - w.WriteHeader(http.StatusAccepted) + span.SetStatus(codes.Ok, "") + w.WriteHeader(statusCode) } type bridgeRoomResolver struct { @@ -102,8 +141,13 @@ type bridgeRoomResolver struct { } func (r bridgeRoomResolver) ResolveManagementRoom(ctx context.Context) (id.RoomID, error) { + ctx, span := observability.StartSpan(ctx, "arrtrix.webhook.resolve_management_room") + defer span.End() + rows, err := r.bridge.DB.Query(ctx, `SELECT mxid, management_room FROM "user" WHERE bridge_id=$1 AND management_room IS NOT NULL AND management_room <> ''`, r.bridge.ID) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return "", fmt.Errorf("failed to query management rooms: %w", err) } defer rows.Close() @@ -113,6 +157,8 @@ func (r bridgeRoomResolver) ResolveManagementRoom(ctx context.Context) (id.RoomI for rows.Next() { var mxid, managementRoom string if err = rows.Scan(&mxid, &managementRoom); err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return "", fmt.Errorf("failed to scan management room: %w", err) } owners = append(owners, id.UserID(mxid)) @@ -121,15 +167,22 @@ func (r bridgeRoomResolver) ResolveManagementRoom(ctx context.Context) (id.RoomI } } if err = rows.Err(); err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return "", fmt.Errorf("failed to iterate management rooms: %w", err) } switch len(owners) { case 0: + span.SetStatus(codes.Error, ErrNoManagementRoom.Error()) return "", ErrNoManagementRoom case 1: + span.SetAttributes(attribute.Int("arrtrix.management_room.count", 1)) + span.SetStatus(codes.Ok, "") return roomID, nil default: + span.SetAttributes(attribute.Int("arrtrix.management_room.count", len(owners))) + span.SetStatus(codes.Error, ErrAmbiguousManagementRoom.Error()) return "", fmt.Errorf("%w: %s", ErrAmbiguousManagementRoom, strings.Join(convertUserIDs(owners), ", ")) } } @@ -139,11 +192,23 @@ type bridgeNoticeSender struct { } func (s bridgeNoticeSender) SendNotice(ctx context.Context, roomID id.RoomID, markdown string) error { + ctx, span := observability.StartSpan(ctx, "arrtrix.webhook.send_notice") + defer span.End() + span.SetAttributes(attribute.String("matrix.room_id", roomID.String())) + if err := s.bridge.Bot.EnsureJoined(ctx, roomID); err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return err } content := format.RenderMarkdown(markdown, true, false) _, err := s.bridge.Bot.SendMessage(ctx, roomID, event.EventMessage, &event.Content{Parsed: &content}, nil) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + return err + } + span.SetStatus(codes.Ok, "") return err } From 9b93f017b626ab750c40dc196fdba5bb5329f08c Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Thu, 16 Apr 2026 10:29:04 +0200 Subject: [PATCH 104/108] Add observability stack: Alloy, Tempo, and OTEL support - Add NixOS modules for Alloy and Tempo with default configs - Update Grafana datasource config for Prometheus, Loki, Tempo - Add Prometheus remote_write for Alloy - Implement OTEL metrics/tracing/logging in arrtrix (Go) - Enable Alloy and Tempo in ulmo system config --- .../nixos/services/media/servarr/default.nix | 2 +- .../services/observability/alloy/default.nix | 80 ++++ .../observability/grafana/default.nix | 52 ++- .../observability/prometheus/default.nix | 21 +- .../services/observability/tempo/default.nix | 48 +++ packages/arrtrix/pkg/observability/config.go | 22 + packages/arrtrix/pkg/observability/otel.go | 397 ++++++++++++++++++ .../arrtrix/pkg/observability/otel_test.go | 54 +++ systems/x86_64-linux/ulmo/default.nix | 4 +- 9 files changed, 661 insertions(+), 19 deletions(-) create mode 100644 modules/nixos/services/observability/alloy/default.nix create mode 100644 modules/nixos/services/observability/tempo/default.nix create mode 100644 packages/arrtrix/pkg/observability/config.go create mode 100644 packages/arrtrix/pkg/observability/otel.go create mode 100644 packages/arrtrix/pkg/observability/otel_test.go diff --git a/modules/nixos/services/media/servarr/default.nix b/modules/nixos/services/media/servarr/default.nix index 23255c0..47461ef 100644 --- a/modules/nixos/services/media/servarr/default.nix +++ b/modules/nixos/services/media/servarr/default.nix @@ -217,7 +217,7 @@ in { { method = 1; # HTTP METHOD 1=POST, 2=PUT name = "Arrtrix"; - url = "http://[::1]${config'.services.arrtrix.settings.appservice.port}"; + url = "http://[::1]${toString config'.services.arrtrix.settings.appservice.port}"; } // (lib.optionalAttrs (lib.elem service ["radarr" "whisparr"]) { onMovieDelete = true; diff --git a/modules/nixos/services/observability/alloy/default.nix b/modules/nixos/services/observability/alloy/default.nix new file mode 100644 index 0000000..8385f8f --- /dev/null +++ b/modules/nixos/services/observability/alloy/default.nix @@ -0,0 +1,80 @@ +{ config, lib, namespace, ... }: +let + inherit (builtins) toString; + inherit (lib) mkEnableOption mkIf; + + cfg = config.${namespace}.services.observability.alloy; + + httpPort = 9007; + otlpGrpcPort = 9010; + otlpHttpPort = 9011; + tempoOtlpGrpcPort = 9009; +in +{ + options.${namespace}.services.observability.alloy = { + enable = mkEnableOption "enable Grafana Alloy"; + }; + + config = mkIf cfg.enable { + services.alloy = { + enable = true; + configPath = "/etc/alloy"; + extraFlags = [ + "--disable-reporting" + "--server.http.listen-addr=0.0.0.0:${toString httpPort}" + "--storage.path=/var/lib/alloy" + ]; + }; + + environment.etc."alloy/config.alloy".text = '' + otelcol.receiver.otlp "default" { + grpc { + endpoint = "127.0.0.1:${toString otlpGrpcPort}" + } + + http { + endpoint = "127.0.0.1:${toString otlpHttpPort}" + } + + output { + metrics = [otelcol.processor.batch.metrics.input] + traces = [otelcol.processor.batch.traces.input] + } + } + + otelcol.processor.batch "metrics" { + output { + metrics = [otelcol.exporter.prometheus.default.input] + } + } + + otelcol.processor.batch "traces" { + output { + traces = [otelcol.exporter.otlp.tempo.input] + } + } + + otelcol.exporter.prometheus "default" { + forward_to = [prometheus.remote_write.local.receiver] + } + + prometheus.remote_write "local" { + endpoint { + url = "http://127.0.0.1:${toString config.services.prometheus.port}/api/v1/write" + } + } + + otelcol.exporter.otlp "tempo" { + client { + endpoint = "127.0.0.1:${toString tempoOtlpGrpcPort}" + + tls { + insecure = true + } + } + } + ''; + + networking.firewall.allowedTCPPorts = [ httpPort ]; + }; +} diff --git a/modules/nixos/services/observability/grafana/default.nix b/modules/nixos/services/observability/grafana/default.nix index a867351..d2ed0e7 100644 --- a/modules/nixos/services/observability/grafana/default.nix +++ b/modules/nixos/services/observability/grafana/default.nix @@ -102,23 +102,43 @@ in { }; datasources.settings.datasources = [ - { - name = "Prometheus"; - type = "prometheus"; - url = "http://localhost:9005"; - isDefault = true; - editable = false; - } + { + name = "Prometheus"; + uid = "prometheus"; + type = "prometheus"; + url = "http://localhost:9002"; + isDefault = true; + editable = false; + } - { - name = "Loki"; - type = "loki"; - url = "http://localhost:9003"; - editable = false; - } - ]; - }; - }; + { + name = "Loki"; + uid = "loki"; + type = "loki"; + url = "http://localhost:9003"; + editable = false; + } + + { + name = "Tempo"; + uid = "tempo"; + type = "tempo"; + url = "http://localhost:9006"; + editable = false; + jsonData = { + nodeGraph.enabled = true; + serviceMap.datasourceUid = "prometheus"; + tracesToLogsV2 = { + datasourceUid = "loki"; + filterByTraceID = true; + spanStartTimeShift = "-1h"; + spanEndTimeShift = "1h"; + }; + }; + } + ]; + }; + }; postgresql = { enable = true; diff --git a/modules/nixos/services/observability/prometheus/default.nix b/modules/nixos/services/observability/prometheus/default.nix index af5ee9d..3faa278 100644 --- a/modules/nixos/services/observability/prometheus/default.nix +++ b/modules/nixos/services/observability/prometheus/default.nix @@ -1,7 +1,7 @@ { pkgs, config, lib, namespace, ... }: let inherit (builtins) toString; - inherit (lib) mkIf mkEnableOption; + inherit (lib) mkEnableOption mkIf optionals; cfg = config.${namespace}.services.observability.prometheus; in @@ -14,6 +14,9 @@ in services.prometheus = { enable = true; port = 9002; + extraFlags = optionals config.${namespace}.services.observability.alloy.enable [ + "--web.enable-remote-write-receiver" + ]; globalConfig.scrape_interval = "15s"; @@ -31,6 +34,22 @@ in { targets = [ "localhost:${toString config.services.prometheus.exporters.node.port}" ]; } ]; } + ] + ++ optionals config.${namespace}.services.observability.alloy.enable [ + { + job_name = "alloy"; + static_configs = [ + { targets = [ "localhost:9007" ]; } + ]; + } + ] + ++ optionals config.${namespace}.services.observability.tempo.enable [ + { + job_name = "tempo"; + static_configs = [ + { targets = [ "localhost:9006" ]; } + ]; + } ]; exporters = { diff --git a/modules/nixos/services/observability/tempo/default.nix b/modules/nixos/services/observability/tempo/default.nix new file mode 100644 index 0000000..10b07d7 --- /dev/null +++ b/modules/nixos/services/observability/tempo/default.nix @@ -0,0 +1,48 @@ +{ config, lib, namespace, ... }: +let + inherit (lib) mkEnableOption mkIf; + + cfg = config.${namespace}.services.observability.tempo; + + httpPort = 9006; + grpcPort = 9008; + otlpGrpcPort = 9009; + otlpHttpPort = 9012; +in +{ + options.${namespace}.services.observability.tempo = { + enable = mkEnableOption "enable Grafana Tempo"; + }; + + config = mkIf cfg.enable { + services.tempo = { + enable = true; + settings = { + auth_enabled = false; + search_enabled = true; + + server = { + http_listen_address = "0.0.0.0"; + http_listen_port = httpPort; + grpc_listen_address = "127.0.0.1"; + grpc_listen_port = grpcPort; + }; + + distributor.receivers.otlp.protocols = { + grpc.endpoint = "127.0.0.1:${builtins.toString otlpGrpcPort}"; + http.endpoint = "127.0.0.1:${builtins.toString otlpHttpPort}"; + }; + + storage.trace = { + backend = "local"; + wal.path = "/var/lib/tempo/wal"; + local.path = "/var/lib/tempo/traces"; + }; + + compactor.compaction.block_retention = "168h"; + }; + }; + + networking.firewall.allowedTCPPorts = [ httpPort ]; + }; +} diff --git a/packages/arrtrix/pkg/observability/config.go b/packages/arrtrix/pkg/observability/config.go new file mode 100644 index 0000000..187c5b5 --- /dev/null +++ b/packages/arrtrix/pkg/observability/config.go @@ -0,0 +1,22 @@ +package observability + +import "strings" + +type Config struct { + OTLPGRPCEndpoint string `yaml:"otlp_grpc_endpoint"` + ServiceName string `yaml:"service_name"` + ResourceAttributes map[string]string `yaml:"resource_attributes"` +} + +func (c *Config) ApplyDefaults() { + if c.ServiceName == "" { + c.ServiceName = "arrtrix" + } + if c.ResourceAttributes == nil { + c.ResourceAttributes = map[string]string{} + } +} + +func (c Config) Enabled() bool { + return strings.TrimSpace(c.OTLPGRPCEndpoint) != "" +} diff --git a/packages/arrtrix/pkg/observability/otel.go b/packages/arrtrix/pkg/observability/otel.go new file mode 100644 index 0000000..2fe46ef --- /dev/null +++ b/packages/arrtrix/pkg/observability/otel.go @@ -0,0 +1,397 @@ +package observability + +import ( + "context" + "errors" + "fmt" + "net/url" + "strings" + "sync" + "time" + + "github.com/rs/zerolog" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + otellog "go.opentelemetry.io/otel/log" + logglobal "go.opentelemetry.io/otel/log/global" + otelmetric "go.opentelemetry.io/otel/metric" + sdklog "go.opentelemetry.io/otel/sdk/log" + sdkmetric "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/trace" +) + +const ( + instrumentationScope = "sneeuwvlok/packages/arrtrix" + logScope = instrumentationScope + "/logs" +) + +type Runtime struct { + traceProvider *sdktrace.TracerProvider + meterProvider *sdkmetric.MeterProvider + logProvider *sdklog.LoggerProvider + logHook zerolog.Hook +} + +type exporterEndpoint struct { + raw string + insecure bool +} + +type instruments struct { + webhookRequests otelCounter + webhookLatency otelHistogram + commandInvocations otelCounter + inviteEvents otelCounter + startupDuration otelHistogram +} + +type otelCounter interface { + Add(context.Context, int64, ...otelmetric.AddOption) +} + +type otelHistogram interface { + Record(context.Context, float64, ...otelmetric.RecordOption) +} + +var ( + mu sync.RWMutex + current instruments + tracer = otel.Tracer(instrumentationScope) + currentReady bool +) + +func Setup(ctx context.Context, cfg Config, version string) (*Runtime, error) { + cfg.ApplyDefaults() + if !cfg.Enabled() { + resetInstruments() + return &Runtime{}, nil + } + + res, err := buildResource(cfg, version) + if err != nil { + return nil, err + } + endpoint, err := parseEndpoint(cfg.OTLPGRPCEndpoint) + if err != nil { + return nil, err + } + + traceExporter, err := otlptracegrpc.New(ctx, traceOptions(endpoint)...) + if err != nil { + return nil, fmt.Errorf("create trace exporter: %w", err) + } + metricExporter, err := otlpmetricgrpc.New(ctx, metricOptions(endpoint)...) + if err != nil { + return nil, fmt.Errorf("create metric exporter: %w", err) + } + logExporter, err := otlploggrpc.New(ctx, logOptions(endpoint)...) + if err != nil { + return nil, fmt.Errorf("create log exporter: %w", err) + } + + traceProvider := sdktrace.NewTracerProvider( + sdktrace.WithResource(res), + sdktrace.WithBatcher(traceExporter), + ) + meterProvider := sdkmetric.NewMeterProvider( + sdkmetric.WithResource(res), + sdkmetric.WithReader(sdkmetric.NewPeriodicReader(metricExporter, sdkmetric.WithInterval(30*time.Second))), + ) + logProvider := sdklog.NewLoggerProvider( + sdklog.WithResource(res), + sdklog.WithProcessor(sdklog.NewBatchProcessor(logExporter)), + ) + + otel.SetTracerProvider(traceProvider) + otel.SetMeterProvider(meterProvider) + logglobal.SetLoggerProvider(logProvider) + + if err = setInstruments(meterProvider); err != nil { + _ = traceProvider.Shutdown(ctx) + _ = meterProvider.Shutdown(ctx) + _ = logProvider.Shutdown(ctx) + return nil, err + } + + tracer = otel.Tracer(instrumentationScope) + return &Runtime{ + traceProvider: traceProvider, + meterProvider: meterProvider, + logProvider: logProvider, + logHook: newLogHook(logglobal.Logger(logScope)), + }, nil +} + +func (r *Runtime) Enabled() bool { + return r != nil && r.traceProvider != nil +} + +func (r *Runtime) LoggerHook() zerolog.Hook { + if r == nil { + return nil + } + return r.logHook +} + +func (r *Runtime) Shutdown(ctx context.Context) error { + if r == nil || !r.Enabled() { + resetInstruments() + return nil + } + + var errs []error + if err := r.logProvider.Shutdown(ctx); err != nil { + errs = append(errs, fmt.Errorf("shutdown log provider: %w", err)) + } + if err := r.meterProvider.Shutdown(ctx); err != nil { + errs = append(errs, fmt.Errorf("shutdown meter provider: %w", err)) + } + if err := r.traceProvider.Shutdown(ctx); err != nil { + errs = append(errs, fmt.Errorf("shutdown trace provider: %w", err)) + } + resetInstruments() + return errors.Join(errs...) +} + +func StartSpan(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, trace.Span) { + return tracer.Start(ctx, name, opts...) +} + +func RecordWebhook(ctx context.Context, eventType, outcome string, statusCode int, duration time.Duration) { + mu.RLock() + inst := current + ready := currentReady + mu.RUnlock() + if !ready { + return + } + attrs := otelmetric.WithAttributes( + attribute.String("event_type", eventType), + attribute.String("outcome", outcome), + attribute.Int("http.status_code", statusCode), + ) + inst.webhookRequests.Add(ctx, 1, attrs) + inst.webhookLatency.Record(ctx, duration.Seconds(), attrs) +} + +func RecordCommand(ctx context.Context, name, outcome string) { + mu.RLock() + inst := current + ready := currentReady + mu.RUnlock() + if !ready { + return + } + inst.commandInvocations.Add(ctx, 1, otelmetric.WithAttributes( + attribute.String("command", name), + attribute.String("outcome", outcome), + )) +} + +func RecordInvite(ctx context.Context, outcome string) { + mu.RLock() + inst := current + ready := currentReady + mu.RUnlock() + if !ready { + return + } + inst.inviteEvents.Add(ctx, 1, otelmetric.WithAttributes(attribute.String("outcome", outcome))) +} + +func RecordStartupPhase(ctx context.Context, phase, outcome string, duration time.Duration) { + mu.RLock() + inst := current + ready := currentReady + mu.RUnlock() + if !ready { + return + } + inst.startupDuration.Record(ctx, duration.Seconds(), otelmetric.WithAttributes( + attribute.String("phase", phase), + attribute.String("outcome", outcome), + )) +} + +func parseEndpoint(raw string) (exporterEndpoint, error) { + raw = strings.TrimSpace(raw) + if raw == "" { + return exporterEndpoint{}, errors.New("observability.otlp_grpc_endpoint must not be empty when observability is enabled") + } + if strings.Contains(raw, "://") { + u, err := url.Parse(raw) + if err != nil { + return exporterEndpoint{}, fmt.Errorf("parse observability.otlp_grpc_endpoint: %w", err) + } + if u.Scheme == "" || u.Host == "" { + return exporterEndpoint{}, fmt.Errorf("invalid observability.otlp_grpc_endpoint %q", raw) + } + return exporterEndpoint{raw: raw, insecure: u.Scheme == "http"}, nil + } + return exporterEndpoint{raw: "http://" + raw, insecure: true}, nil +} + +func buildResource(cfg Config, version string) (*resource.Resource, error) { + attrs := []attribute.KeyValue{ + attribute.String("service.name", cfg.ServiceName), + } + if version != "" { + attrs = append(attrs, attribute.String("service.version", version)) + } + for key, value := range cfg.ResourceAttributes { + attrs = append(attrs, attribute.String(key, value)) + } + return resource.Merge(resource.Default(), resource.NewWithAttributes("", attrs...)) +} + +func setInstruments(provider *sdkmetric.MeterProvider) error { + meter := provider.Meter(instrumentationScope) + + webhookRequests, err := meter.Int64Counter( + "arrtrix.webhook.requests", + otelmetric.WithDescription("Number of Arr webhook requests handled by arrtrix."), + ) + if err != nil { + return fmt.Errorf("create webhook request counter: %w", err) + } + webhookLatency, err := meter.Float64Histogram( + "arrtrix.webhook.duration.seconds", + otelmetric.WithDescription("Duration of Arr webhook request handling."), + otelmetric.WithUnit("s"), + ) + if err != nil { + return fmt.Errorf("create webhook duration histogram: %w", err) + } + commandInvocations, err := meter.Int64Counter( + "arrtrix.matrix.commands", + otelmetric.WithDescription("Number of Matrix management-room commands handled by arrtrix."), + ) + if err != nil { + return fmt.Errorf("create command counter: %w", err) + } + inviteEvents, err := meter.Int64Counter( + "arrtrix.matrix.invites", + otelmetric.WithDescription("Number of management-room invite flows observed by arrtrix."), + ) + if err != nil { + return fmt.Errorf("create invite counter: %w", err) + } + startupDuration, err := meter.Float64Histogram( + "arrtrix.runtime.phase.duration.seconds", + otelmetric.WithDescription("Duration of arrtrix runtime startup and shutdown phases."), + otelmetric.WithUnit("s"), + ) + if err != nil { + return fmt.Errorf("create runtime duration histogram: %w", err) + } + + mu.Lock() + current = instruments{ + webhookRequests: webhookRequests, + webhookLatency: webhookLatency, + commandInvocations: commandInvocations, + inviteEvents: inviteEvents, + startupDuration: startupDuration, + } + currentReady = true + mu.Unlock() + return nil +} + +func resetInstruments() { + mu.Lock() + current = instruments{} + currentReady = false + mu.Unlock() +} + +func traceOptions(endpoint exporterEndpoint) []otlptracegrpc.Option { + opts := []otlptracegrpc.Option{otlptracegrpc.WithEndpointURL(endpoint.raw)} + if endpoint.insecure { + opts = append(opts, otlptracegrpc.WithInsecure()) + } + return opts +} + +func metricOptions(endpoint exporterEndpoint) []otlpmetricgrpc.Option { + opts := []otlpmetricgrpc.Option{otlpmetricgrpc.WithEndpointURL(endpoint.raw)} + if endpoint.insecure { + opts = append(opts, otlpmetricgrpc.WithInsecure()) + } + return opts +} + +func logOptions(endpoint exporterEndpoint) []otlploggrpc.Option { + opts := []otlploggrpc.Option{otlploggrpc.WithEndpointURL(endpoint.raw)} + if endpoint.insecure { + opts = append(opts, otlploggrpc.WithInsecure()) + } + return opts +} + +type otelLogHook struct { + logger otellog.Logger +} + +func newLogHook(logger otellog.Logger) zerolog.Hook { + return otelLogHook{logger: logger} +} + +func (h otelLogHook) Run(e *zerolog.Event, level zerolog.Level, message string) { + if h.logger == nil { + return + } + ctx := e.GetCtx() + if ctx == nil { + ctx = context.Background() + } + + severity := mapSeverity(level) + if !h.logger.Enabled(ctx, otellog.EnabledParameters{Severity: severity}) { + return + } + + now := time.Now() + record := otellog.Record{} + record.SetTimestamp(now) + record.SetObservedTimestamp(now) + record.SetSeverity(severity) + record.SetSeverityText(strings.ToUpper(level.String())) + record.SetBody(otellog.StringValue(message)) + record.AddAttributes(otellog.String("log.scope", logScope)) + + if spanCtx := trace.SpanContextFromContext(ctx); spanCtx.IsValid() { + record.AddAttributes( + otellog.String("trace_id", spanCtx.TraceID().String()), + otellog.String("span_id", spanCtx.SpanID().String()), + ) + } + + h.logger.Emit(ctx, record) +} + +func mapSeverity(level zerolog.Level) otellog.Severity { + switch level { + case zerolog.TraceLevel: + return otellog.SeverityTrace + case zerolog.DebugLevel: + return otellog.SeverityDebug + case zerolog.InfoLevel: + return otellog.SeverityInfo + case zerolog.WarnLevel: + return otellog.SeverityWarn + case zerolog.ErrorLevel: + return otellog.SeverityError + case zerolog.FatalLevel: + return otellog.SeverityFatal + case zerolog.PanicLevel: + return otellog.SeverityFatal4 + default: + return otellog.SeverityUndefined + } +} diff --git a/packages/arrtrix/pkg/observability/otel_test.go b/packages/arrtrix/pkg/observability/otel_test.go new file mode 100644 index 0000000..4dd8e3e --- /dev/null +++ b/packages/arrtrix/pkg/observability/otel_test.go @@ -0,0 +1,54 @@ +package observability + +import "testing" + +func TestConfigDefaults(t *testing.T) { + var cfg Config + cfg.ApplyDefaults() + + if cfg.ServiceName != "arrtrix" { + t.Fatalf("expected default service name arrtrix, got %q", cfg.ServiceName) + } + if cfg.ResourceAttributes == nil { + t.Fatal("expected resource attributes map to be initialized") + } + if cfg.Enabled() { + t.Fatal("expected observability to be disabled by default") + } +} + +func TestParseEndpointSupportsURLAndBareHost(t *testing.T) { + tests := []struct { + name string + input string + wantRaw string + insecure bool + wantError bool + }{ + {name: "https url", input: "https://otel.example:4317", wantRaw: "https://otel.example:4317"}, + {name: "http url", input: "http://127.0.0.1:4317", wantRaw: "http://127.0.0.1:4317", insecure: true}, + {name: "bare host", input: "collector:4317", wantRaw: "http://collector:4317", insecure: true}, + {name: "invalid", input: "://bad", wantError: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := parseEndpoint(tt.input) + if tt.wantError { + if err == nil { + t.Fatal("expected error") + } + return + } + if err != nil { + t.Fatalf("parseEndpoint returned error: %v", err) + } + if got.raw != tt.wantRaw { + t.Fatalf("expected raw endpoint %q, got %q", tt.wantRaw, got.raw) + } + if got.insecure != tt.insecure { + t.Fatalf("expected insecure=%t, got %t", tt.insecure, got.insecure) + } + }) + } +} diff --git a/systems/x86_64-linux/ulmo/default.nix b/systems/x86_64-linux/ulmo/default.nix index 7c20a11..57f57d3 100644 --- a/systems/x86_64-linux/ulmo/default.nix +++ b/systems/x86_64-linux/ulmo/default.nix @@ -256,10 +256,12 @@ }; observability = { + alloy.enable = true; grafana.enable = true; - prometheus.enable = true; loki.enable = true; + prometheus.enable = true; promtail.enable = true; + tempo.enable = true; # uptime-kuma.enable = true; }; From e26e25b566a5d48865d413f7f87302054affcdfa Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Thu, 16 Apr 2026 10:41:16 +0200 Subject: [PATCH 105/108] Change observability service ports and add Arrtrix content management - Update ports for Alloy, Grafana, Loki, Prometheus, Promtail, Tempo, and Uptime Kuma to new ranges - Add Arrtrix content management commands and subscriptions - Implement Radarr and Sonarr client logic for movie and series management - Add matrix commands for download and subscription management - Add subscription repository with database schema and logic - Update Arrtrix config and example config for content section - Update help text and command processor to include new commands - Update vendor hash for Arrtrix package --- .../services/observability/alloy/default.nix | 8 +- .../observability/grafana/default.nix | 8 +- .../services/observability/loki/default.nix | 4 +- .../observability/prometheus/default.nix | 12 +- .../observability/promtail/default.nix | 6 +- .../services/observability/tempo/default.nix | 8 +- .../observability/uptime-kuma/default.nix | 4 +- packages/arrtrix/default.nix | 2 +- packages/arrtrix/pkg/arr/catalog.go | 76 ++++++ packages/arrtrix/pkg/arrclient/client.go | 211 +++++++++++++++++ packages/arrtrix/pkg/arrclient/radarr.go | 164 +++++++++++++ packages/arrtrix/pkg/arrclient/sonarr.go | 149 ++++++++++++ packages/arrtrix/pkg/connector/config.go | 42 +++- packages/arrtrix/pkg/connector/connector.go | 30 ++- .../arrtrix/pkg/connector/example-config.yaml | 23 +- packages/arrtrix/pkg/matrixcmd/download.go | 222 ++++++++++++++++++ packages/arrtrix/pkg/matrixcmd/help_test.go | 2 + packages/arrtrix/pkg/matrixcmd/processor.go | 2 + .../arrtrix/pkg/matrixcmd/subscriptions.go | 107 +++++++++ packages/arrtrix/pkg/runtime/main.go | 5 + packages/arrtrix/pkg/subscriptions/repo.go | 141 +++++++++++ packages/arrtrix/pkg/webhook/arr.go | 180 ++++++++++---- packages/arrtrix/pkg/webhook/arr_test.go | 14 +- systems/x86_64-linux/ulmo/default.nix | 2 +- 24 files changed, 1340 insertions(+), 82 deletions(-) create mode 100644 packages/arrtrix/pkg/arr/catalog.go create mode 100644 packages/arrtrix/pkg/arrclient/client.go create mode 100644 packages/arrtrix/pkg/arrclient/radarr.go create mode 100644 packages/arrtrix/pkg/arrclient/sonarr.go create mode 100644 packages/arrtrix/pkg/matrixcmd/download.go create mode 100644 packages/arrtrix/pkg/matrixcmd/subscriptions.go create mode 100644 packages/arrtrix/pkg/subscriptions/repo.go diff --git a/modules/nixos/services/observability/alloy/default.nix b/modules/nixos/services/observability/alloy/default.nix index 8385f8f..4b6d787 100644 --- a/modules/nixos/services/observability/alloy/default.nix +++ b/modules/nixos/services/observability/alloy/default.nix @@ -5,10 +5,10 @@ let cfg = config.${namespace}.services.observability.alloy; - httpPort = 9007; - otlpGrpcPort = 9010; - otlpHttpPort = 9011; - tempoOtlpGrpcPort = 9009; + httpPort = 9700; + otlpGrpcPort = 9701; + otlpHttpPort = 9702; + tempoOtlpGrpcPort = 9602; in { options.${namespace}.services.observability.alloy = { diff --git a/modules/nixos/services/observability/grafana/default.nix b/modules/nixos/services/observability/grafana/default.nix index d2ed0e7..05fb1da 100644 --- a/modules/nixos/services/observability/grafana/default.nix +++ b/modules/nixos/services/observability/grafana/default.nix @@ -25,7 +25,7 @@ in { settings = { server = { - http_port = 9001; + http_port = 9100; http_addr = "0.0.0.0"; domain = "ulmo"; }; @@ -106,7 +106,7 @@ in { name = "Prometheus"; uid = "prometheus"; type = "prometheus"; - url = "http://localhost:9002"; + url = "http://localhost:9200"; isDefault = true; editable = false; } @@ -115,7 +115,7 @@ in { name = "Loki"; uid = "loki"; type = "loki"; - url = "http://localhost:9003"; + url = "http://localhost:9300"; editable = false; } @@ -123,7 +123,7 @@ in { name = "Tempo"; uid = "tempo"; type = "tempo"; - url = "http://localhost:9006"; + url = "http://localhost:9600"; editable = false; jsonData = { nodeGraph.enabled = true; diff --git a/modules/nixos/services/observability/loki/default.nix b/modules/nixos/services/observability/loki/default.nix index d4774ac..e99448e 100644 --- a/modules/nixos/services/observability/loki/default.nix +++ b/modules/nixos/services/observability/loki/default.nix @@ -17,7 +17,7 @@ in auth_enabled = false; server = { - http_listen_port = 9003; + http_listen_port = 9300; }; common = { @@ -44,6 +44,6 @@ in }; }; - networking.firewall.allowedTCPPorts = [ 9003 ]; + networking.firewall.allowedTCPPorts = [ 9300 ]; }; } diff --git a/modules/nixos/services/observability/prometheus/default.nix b/modules/nixos/services/observability/prometheus/default.nix index 3faa278..fc09e01 100644 --- a/modules/nixos/services/observability/prometheus/default.nix +++ b/modules/nixos/services/observability/prometheus/default.nix @@ -13,7 +13,7 @@ in config = mkIf cfg.enable { services.prometheus = { enable = true; - port = 9002; + port = 9200; extraFlags = optionals config.${namespace}.services.observability.alloy.enable [ "--web.enable-remote-write-receiver" ]; @@ -24,7 +24,7 @@ in { job_name = "prometheus"; static_configs = [ - { targets = [ "localhost:9002" ]; } + { targets = [ "localhost:9200" ]; } ]; } @@ -39,7 +39,7 @@ in { job_name = "alloy"; static_configs = [ - { targets = [ "localhost:9007" ]; } + { targets = [ "localhost:9700" ]; } ]; } ] @@ -47,7 +47,7 @@ in { job_name = "tempo"; static_configs = [ - { targets = [ "localhost:9006" ]; } + { targets = [ "localhost:9600" ]; } ]; } ]; @@ -55,13 +55,13 @@ in exporters = { node = { enable = true; - port = 9005; + port = 9201; enabledCollectors = [ "systemd" ]; openFirewall = true; }; }; }; - networking.firewall.allowedTCPPorts = [ 9002 ]; + networking.firewall.allowedTCPPorts = [ 9200 ]; }; } diff --git a/modules/nixos/services/observability/promtail/default.nix b/modules/nixos/services/observability/promtail/default.nix index 38dbbab..40a1b87 100644 --- a/modules/nixos/services/observability/promtail/default.nix +++ b/modules/nixos/services/observability/promtail/default.nix @@ -25,7 +25,7 @@ in { configuration = { server = { - http_listen_port = 9004; + http_listen_port = 9400; grpc_listen_port = 0; }; @@ -35,7 +35,7 @@ in { clients = [ { - url = "http://[::1]:9003/loki/api/v1/push"; + url = "http://[::1]:9300/loki/api/v1/push"; } ]; @@ -60,6 +60,6 @@ in { }; }; - networking.firewall.allowedTCPPorts = [9004]; + networking.firewall.allowedTCPPorts = [9400]; }; } diff --git a/modules/nixos/services/observability/tempo/default.nix b/modules/nixos/services/observability/tempo/default.nix index 10b07d7..9a6bd89 100644 --- a/modules/nixos/services/observability/tempo/default.nix +++ b/modules/nixos/services/observability/tempo/default.nix @@ -4,10 +4,10 @@ let cfg = config.${namespace}.services.observability.tempo; - httpPort = 9006; - grpcPort = 9008; - otlpGrpcPort = 9009; - otlpHttpPort = 9012; + httpPort = 9600; + grpcPort = 9601; + otlpGrpcPort = 9602; + otlpHttpPort = 9603; in { options.${namespace}.services.observability.tempo = { diff --git a/modules/nixos/services/observability/uptime-kuma/default.nix b/modules/nixos/services/observability/uptime-kuma/default.nix index c23977b..f4dcde4 100644 --- a/modules/nixos/services/observability/uptime-kuma/default.nix +++ b/modules/nixos/services/observability/uptime-kuma/default.nix @@ -15,11 +15,11 @@ in enable = true; settings = { - PORT = toString 9006; + PORT = toString 9500; HOST = "0.0.0.0"; }; }; - networking.firewall.allowedTCPPorts = [ 9006 ]; + networking.firewall.allowedTCPPorts = [ 9500 ]; }; } diff --git a/packages/arrtrix/default.nix b/packages/arrtrix/default.nix index 81950f9..0113edb 100644 --- a/packages/arrtrix/default.nix +++ b/packages/arrtrix/default.nix @@ -11,7 +11,7 @@ buildGoModule rec { src = lib.cleanSource ./.; - vendorHash = "sha256-FbatoXcxZcnqVUmoj/jeSMFO/iTmD8uga47MoTdGcRw="; + vendorHash = "sha256-UYRit+v41djnCx+GFdEl/8WQsp2DzF4ywT9iv3m1pSc="; subPackages = ["cmd/arrtrix"]; buildInputs = [olm]; diff --git a/packages/arrtrix/pkg/arr/catalog.go b/packages/arrtrix/pkg/arr/catalog.go new file mode 100644 index 0000000..eb2f833 --- /dev/null +++ b/packages/arrtrix/pkg/arr/catalog.go @@ -0,0 +1,76 @@ +package arr + +import ( + "fmt" + "slices" + "strings" +) + +type ContentType string + +const ( + ContentTypeMovies ContentType = "movies" + ContentTypeSeries ContentType = "series" +) + +var supportedContentTypes = []ContentType{ + ContentTypeMovies, + ContentTypeSeries, +} + +var supportedEvents = map[ContentType][]string{ + ContentTypeMovies: {"Test", "Grab", "Download", "Rename", "MovieFileDelete", "MovieDelete"}, + ContentTypeSeries: {"Test", "Grab", "Download", "Rename", "EpisodeFileDelete", "SeriesDelete"}, +} + +func SupportedContentTypes() []ContentType { + return append([]ContentType(nil), supportedContentTypes...) +} + +func SupportedEventTypes(contentType ContentType) []string { + return append([]string(nil), supportedEvents[contentType]...) +} + +func ParseContentType(value string) (ContentType, error) { + contentType := ContentType(strings.ToLower(strings.TrimSpace(value))) + if slices.Contains(supportedContentTypes, contentType) { + return contentType, nil + } + return "", fmt.Errorf("unsupported content type %q (expected one of: %s)", value, Strings()) +} + +func ParseEventType(contentType ContentType, value string) (string, error) { + value = strings.TrimSpace(value) + if strings.EqualFold(value, "all") { + return "all", nil + } + for _, eventType := range supportedEvents[contentType] { + if strings.EqualFold(eventType, value) { + return eventType, nil + } + } + return "", fmt.Errorf("unsupported event type %q for %s", value, contentType) +} + +func SupportsEventType(contentType ContentType, eventType string) bool { + return slices.Contains(supportedEvents[contentType], strings.TrimSpace(eventType)) +} + +func (c ContentType) Label() string { + switch c { + case ContentTypeMovies: + return "movies" + case ContentTypeSeries: + return "series" + default: + return string(c) + } +} + +func Strings() string { + values := make([]string, 0, len(supportedContentTypes)) + for _, contentType := range supportedContentTypes { + values = append(values, string(contentType)) + } + return strings.Join(values, ", ") +} diff --git a/packages/arrtrix/pkg/arrclient/client.go b/packages/arrtrix/pkg/arrclient/client.go new file mode 100644 index 0000000..558dc52 --- /dev/null +++ b/packages/arrtrix/pkg/arrclient/client.go @@ -0,0 +1,211 @@ +package arrclient + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "path" + "strings" + + "sneeuwvlok/packages/arrtrix/pkg/arr" +) + +type Client interface { + ContentType() arr.ContentType + Search(context.Context, string) ([]SearchResult, error) + List(context.Context, string) ([]ManagedItem, error) + Add(context.Context, SearchResult) (*ManagedItem, error) + SetMonitored(context.Context, int64, bool) (*ManagedItem, error) + Delete(context.Context, int64) error +} + +type SearchResult struct { + LookupID int64 + Title string + Year int + Overview string +} + +type ManagedItem struct { + ID int64 + LookupID int64 + Title string + Year int + Monitored bool + Path string +} + +type RadarrConfig struct { + URL string `yaml:"url"` + APIKey string `yaml:"api_key"` + RootFolderPath string `yaml:"root_folder_path"` + QualityProfileID int64 `yaml:"quality_profile_id"` + MinimumAvailability string `yaml:"minimum_availability"` + SearchOnAdd *bool `yaml:"search_on_add"` +} + +type SonarrConfig struct { + URL string `yaml:"url"` + APIKey string `yaml:"api_key"` + RootFolderPath string `yaml:"root_folder_path"` + QualityProfileID int64 `yaml:"quality_profile_id"` + LanguageProfileID int64 `yaml:"language_profile_id"` + SeasonFolder *bool `yaml:"season_folder"` + SeriesType string `yaml:"series_type"` + SearchOnAdd *bool `yaml:"search_on_add"` +} + +type httpClient struct { + baseURL *url.URL + apiKey string + httpClient *http.Client +} + +func (c *RadarrConfig) ApplyDefaults() { + if c.MinimumAvailability == "" { + c.MinimumAvailability = "released" + } +} + +func (c RadarrConfig) Enabled() bool { + return strings.TrimSpace(c.URL) != "" || strings.TrimSpace(c.APIKey) != "" +} + +func (c RadarrConfig) Validate() error { + if !c.Enabled() { + return nil + } + switch { + case strings.TrimSpace(c.URL) == "": + return fmt.Errorf("network.content.movies.url must be set when movies content is configured") + case strings.TrimSpace(c.APIKey) == "": + return fmt.Errorf("network.content.movies.api_key must be set when movies content is configured") + case strings.TrimSpace(c.RootFolderPath) == "": + return fmt.Errorf("network.content.movies.root_folder_path must be set when movies content is configured") + case c.QualityProfileID <= 0: + return fmt.Errorf("network.content.movies.quality_profile_id must be set when movies content is configured") + case strings.TrimSpace(c.MinimumAvailability) == "": + return fmt.Errorf("network.content.movies.minimum_availability must not be empty") + default: + return nil + } +} + +func (c RadarrConfig) SearchOnAddValue() bool { + return boolValue(c.SearchOnAdd, true) +} + +func (c *SonarrConfig) ApplyDefaults() { + if c.SeriesType == "" { + c.SeriesType = "standard" + } +} + +func (c SonarrConfig) Enabled() bool { + return strings.TrimSpace(c.URL) != "" || strings.TrimSpace(c.APIKey) != "" +} + +func (c SonarrConfig) Validate() error { + if !c.Enabled() { + return nil + } + switch { + case strings.TrimSpace(c.URL) == "": + return fmt.Errorf("network.content.series.url must be set when series content is configured") + case strings.TrimSpace(c.APIKey) == "": + return fmt.Errorf("network.content.series.api_key must be set when series content is configured") + case strings.TrimSpace(c.RootFolderPath) == "": + return fmt.Errorf("network.content.series.root_folder_path must be set when series content is configured") + case c.QualityProfileID <= 0: + return fmt.Errorf("network.content.series.quality_profile_id must be set when series content is configured") + case c.LanguageProfileID <= 0: + return fmt.Errorf("network.content.series.language_profile_id must be set when series content is configured") + case strings.TrimSpace(c.SeriesType) == "": + return fmt.Errorf("network.content.series.series_type must not be empty") + default: + return nil + } +} + +func (c SonarrConfig) SeasonFolderValue() bool { + return boolValue(c.SeasonFolder, true) +} + +func (c SonarrConfig) SearchOnAddValue() bool { + return boolValue(c.SearchOnAdd, true) +} + +func newHTTPClient(rawURL, apiKey string) (*httpClient, error) { + parsedURL, err := url.Parse(strings.TrimRight(strings.TrimSpace(rawURL), "/")) + if err != nil { + return nil, err + } + return &httpClient{ + baseURL: parsedURL, + apiKey: apiKey, + httpClient: http.DefaultClient, + }, nil +} + +func (c *httpClient) do(ctx context.Context, method, requestPath string, query url.Values, body any, dest any) error { + endpoint := *c.baseURL + endpoint.Path = path.Join(endpoint.Path, requestPath) + if len(query) > 0 { + endpoint.RawQuery = query.Encode() + } + + var payload io.Reader + if body != nil { + data, err := json.Marshal(body) + if err != nil { + return err + } + payload = bytes.NewReader(data) + } + + req, err := http.NewRequestWithContext(ctx, method, endpoint.String(), payload) + if err != nil { + return err + } + req.Header.Set("X-Api-Key", c.apiKey) + if body != nil { + req.Header.Set("Content-Type", "application/json") + } + + resp, err := c.httpClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + data, _ := io.ReadAll(io.LimitReader(resp.Body, 4096)) + return fmt.Errorf("%s %s returned %d: %s", method, endpoint.String(), resp.StatusCode, strings.TrimSpace(string(data))) + } + if dest == nil { + return nil + } + return json.NewDecoder(resp.Body).Decode(dest) +} + +func boolValue(value *bool, fallback bool) bool { + if value == nil { + return fallback + } + return *value +} + +func containsFold(haystack, needle string) bool { + return strings.Contains(strings.ToLower(haystack), strings.ToLower(strings.TrimSpace(needle))) +} + +func FormatSearchResult(result SearchResult) string { + if result.Year != 0 { + return fmt.Sprintf("%s (%d)", result.Title, result.Year) + } + return result.Title +} diff --git a/packages/arrtrix/pkg/arrclient/radarr.go b/packages/arrtrix/pkg/arrclient/radarr.go new file mode 100644 index 0000000..21ac1fd --- /dev/null +++ b/packages/arrtrix/pkg/arrclient/radarr.go @@ -0,0 +1,164 @@ +package arrclient + +import ( + "context" + "fmt" + "net/http" + "net/url" + "strconv" + "strings" + + "sneeuwvlok/packages/arrtrix/pkg/arr" +) + +type RadarrClient struct { + http *httpClient + config RadarrConfig +} + +type radarrMovie struct { + ID int64 `json:"id"` + Title string `json:"title"` + Year int `json:"year"` + TMDBID int64 `json:"tmdbId"` + Overview string `json:"overview"` + Monitored bool `json:"monitored"` + Path string `json:"path"` +} + +func NewRadarrClient(config RadarrConfig) (*RadarrClient, error) { + config.ApplyDefaults() + if err := config.Validate(); err != nil { + return nil, err + } + httpClient, err := newHTTPClient(config.URL, config.APIKey) + if err != nil { + return nil, err + } + return &RadarrClient{http: httpClient, config: config}, nil +} + +func (c *RadarrClient) ContentType() arr.ContentType { + return arr.ContentTypeMovies +} + +func (c *RadarrClient) Search(ctx context.Context, query string) ([]SearchResult, error) { + var response []radarrMovie + if err := c.http.do(ctx, http.MethodGet, "/api/v3/movie/lookup", url.Values{"term": {strings.TrimSpace(query)}}, nil, &response); err != nil { + return nil, err + } + + results := make([]SearchResult, 0, len(response)) + for _, movie := range response { + if movie.TMDBID == 0 { + continue + } + results = append(results, SearchResult{ + LookupID: movie.TMDBID, + Title: movie.Title, + Year: movie.Year, + Overview: movie.Overview, + }) + } + return results, nil +} + +func (c *RadarrClient) List(ctx context.Context, query string) ([]ManagedItem, error) { + var response []radarrMovie + if err := c.http.do(ctx, http.MethodGet, "/api/v3/movie", nil, nil, &response); err != nil { + return nil, err + } + + items := make([]ManagedItem, 0, len(response)) + for _, movie := range response { + if query != "" && !containsFold(movie.Title, query) && !containsFold(strconv.Itoa(movie.Year), query) { + continue + } + items = append(items, ManagedItem{ + ID: movie.ID, + LookupID: movie.TMDBID, + Title: movie.Title, + Year: movie.Year, + Monitored: movie.Monitored, + Path: movie.Path, + }) + } + return items, nil +} + +func (c *RadarrClient) Add(ctx context.Context, result SearchResult) (*ManagedItem, error) { + payload := map[string]any{ + "title": result.Title, + "tmdbId": result.LookupID, + "year": result.Year, + "qualityProfileId": c.config.QualityProfileID, + "rootFolderPath": c.config.RootFolderPath, + "minimumAvailability": c.config.MinimumAvailability, + "monitored": true, + "addOptions": map[string]any{ + "searchForMovie": c.config.SearchOnAddValue(), + }, + } + + var response radarrMovie + if err := c.http.do(ctx, http.MethodPost, "/api/v3/movie", nil, payload, &response); err != nil { + return nil, err + } + item := ManagedItem{ + ID: response.ID, + LookupID: response.TMDBID, + Title: response.Title, + Year: response.Year, + Monitored: response.Monitored, + Path: response.Path, + } + return &item, nil +} + +func (c *RadarrClient) SetMonitored(ctx context.Context, id int64, monitored bool) (*ManagedItem, error) { + var movie map[string]any + endpoint := "/api/v3/movie/" + strconv.FormatInt(id, 10) + if err := c.http.do(ctx, http.MethodGet, endpoint, nil, nil, &movie); err != nil { + return nil, err + } + movie["monitored"] = monitored + + var response radarrMovie + if err := c.http.do(ctx, http.MethodPut, endpoint, nil, movie, &response); err != nil { + return nil, err + } + item := ManagedItem{ + ID: response.ID, + LookupID: response.TMDBID, + Title: response.Title, + Year: response.Year, + Monitored: response.Monitored, + Path: response.Path, + } + return &item, nil +} + +func (c *RadarrClient) Delete(ctx context.Context, id int64) error { + return c.http.do(ctx, http.MethodDelete, "/api/v3/movie/"+strconv.FormatInt(id, 10), url.Values{ + "deleteFiles": {"false"}, + "addImportExclusion": {"false"}, + }, nil, nil) +} + +func PickSingleResult(results []SearchResult, query string) (SearchResult, error) { + switch len(results) { + case 0: + return SearchResult{}, fmt.Errorf("no matching result found for %q", query) + case 1: + return results[0], nil + default: + normalized := strings.TrimSpace(strings.ToLower(query)) + for _, result := range results { + title := strings.ToLower(FormatSearchResult(result)) + if title == normalized { + return result, nil + } + } + return SearchResult{}, fmt.Errorf("multiple results matched %q", query) + } +} diff --git a/packages/arrtrix/pkg/arrclient/sonarr.go b/packages/arrtrix/pkg/arrclient/sonarr.go new file mode 100644 index 0000000..9b0691b --- /dev/null +++ b/packages/arrtrix/pkg/arrclient/sonarr.go @@ -0,0 +1,149 @@ +package arrclient + +import ( + "context" + "net/http" + "net/url" + "strconv" + "strings" + + "sneeuwvlok/packages/arrtrix/pkg/arr" +) + +type SonarrClient struct { + http *httpClient + config SonarrConfig +} + +type sonarrSeries struct { + ID int64 `json:"id"` + Title string `json:"title"` + Year int `json:"year"` + TVDBID int64 `json:"tvdbId"` + Overview string `json:"overview"` + Monitored bool `json:"monitored"` + Path string `json:"path"` +} + +func NewSonarrClient(config SonarrConfig) (*SonarrClient, error) { + config.ApplyDefaults() + if err := config.Validate(); err != nil { + return nil, err + } + httpClient, err := newHTTPClient(config.URL, config.APIKey) + if err != nil { + return nil, err + } + return &SonarrClient{http: httpClient, config: config}, nil +} + +func (c *SonarrClient) ContentType() arr.ContentType { + return arr.ContentTypeSeries +} + +func (c *SonarrClient) Search(ctx context.Context, query string) ([]SearchResult, error) { + var response []sonarrSeries + if err := c.http.do(ctx, http.MethodGet, "/api/v3/series/lookup", url.Values{"term": {strings.TrimSpace(query)}}, nil, &response); err != nil { + return nil, err + } + + results := make([]SearchResult, 0, len(response)) + for _, series := range response { + if series.TVDBID == 0 { + continue + } + results = append(results, SearchResult{ + LookupID: series.TVDBID, + Title: series.Title, + Year: series.Year, + Overview: series.Overview, + }) + } + return results, nil +} + +func (c *SonarrClient) List(ctx context.Context, query string) ([]ManagedItem, error) { + var response []sonarrSeries + if err := c.http.do(ctx, http.MethodGet, "/api/v3/series", nil, nil, &response); err != nil { + return nil, err + } + + items := make([]ManagedItem, 0, len(response)) + for _, series := range response { + if query != "" && !containsFold(series.Title, query) && !containsFold(strconv.Itoa(series.Year), query) { + continue + } + items = append(items, ManagedItem{ + ID: series.ID, + LookupID: series.TVDBID, + Title: series.Title, + Year: series.Year, + Monitored: series.Monitored, + Path: series.Path, + }) + } + return items, nil +} + +func (c *SonarrClient) Add(ctx context.Context, result SearchResult) (*ManagedItem, error) { + payload := map[string]any{ + "title": result.Title, + "tvdbId": result.LookupID, + "qualityProfileId": c.config.QualityProfileID, + "languageProfileId": c.config.LanguageProfileID, + "rootFolderPath": c.config.RootFolderPath, + "seasonFolder": c.config.SeasonFolderValue(), + "monitored": true, + "seriesType": c.config.SeriesType, + "addOptions": map[string]any{ + "searchForMissingEpisodes": c.config.SearchOnAddValue(), + }, + } + if result.Year != 0 { + payload["year"] = result.Year + } + + var response sonarrSeries + if err := c.http.do(ctx, http.MethodPost, "/api/v3/series", nil, payload, &response); err != nil { + return nil, err + } + item := ManagedItem{ + ID: response.ID, + LookupID: response.TVDBID, + Title: response.Title, + Year: response.Year, + Monitored: response.Monitored, + Path: response.Path, + } + return &item, nil +} + +func (c *SonarrClient) SetMonitored(ctx context.Context, id int64, monitored bool) (*ManagedItem, error) { + var series map[string]any + endpoint := "/api/v3/series/" + strconv.FormatInt(id, 10) + if err := c.http.do(ctx, http.MethodGet, endpoint, nil, nil, &series); err != nil { + return nil, err + } + series["monitored"] = monitored + + var response sonarrSeries + if err := c.http.do(ctx, http.MethodPut, endpoint, nil, series, &response); err != nil { + return nil, err + } + item := ManagedItem{ + ID: response.ID, + LookupID: response.TVDBID, + Title: response.Title, + Year: response.Year, + Monitored: response.Monitored, + Path: response.Path, + } + return &item, nil +} + +func (c *SonarrClient) Delete(ctx context.Context, id int64) error { + return c.http.do(ctx, http.MethodDelete, "/api/v3/series/"+strconv.FormatInt(id, 10), url.Values{ + "deleteFiles": {"false"}, + "addImportListExclusion": {"false"}, + }, nil, nil) +} diff --git a/packages/arrtrix/pkg/connector/config.go b/packages/arrtrix/pkg/connector/config.go index 2cdec34..149fd32 100644 --- a/packages/arrtrix/pkg/connector/config.go +++ b/packages/arrtrix/pkg/connector/config.go @@ -8,13 +8,23 @@ import ( up "go.mau.fi/util/configupgrade" "maunium.net/go/mautrix/bridgev2" + "sneeuwvlok/packages/arrtrix/pkg/arr" + "sneeuwvlok/packages/arrtrix/pkg/arrclient" + "sneeuwvlok/packages/arrtrix/pkg/subscriptions" "sneeuwvlok/packages/arrtrix/pkg/webhook" ) //go:embed example-config.yaml var ExampleConfig string -type Config struct{} +type Config struct { + Content ContentConfig `yaml:"content"` +} + +type ContentConfig struct { + Movies arrclient.RadarrConfig `yaml:"movies"` + Series arrclient.SonarrConfig `yaml:"series"` +} func upgradeConfig(helper up.Helper) {} @@ -23,6 +33,14 @@ func (s *ArrtrixConnector) GetConfig() (string, any, up.Upgrader) { } func (s *ArrtrixConnector) ValidateConfig() error { + s.Config.Content.Movies.ApplyDefaults() + s.Config.Content.Series.ApplyDefaults() + if err := s.Config.Content.Movies.Validate(); err != nil { + return err + } + if err := s.Config.Content.Series.Validate(); err != nil { + return err + } return nil } @@ -30,7 +48,27 @@ func (s *ArrtrixConnector) MountRoutes(router *http.ServeMux) error { if s.Bridge == nil { return fmt.Errorf("bridge is not initialized") } - return webhook.MountArr(router, s.Bridge) + return webhook.MountArr(router, s.Bridge, s.Subscriptions()) } var _ bridgev2.ConfigValidatingNetwork = (*ArrtrixConnector)(nil) +var _ webhook.SubscriptionFilter = (*subscriptions.Repository)(nil) + +func (c ContentConfig) Client(contentType arr.ContentType) (arrclient.Client, bool, error) { + switch contentType { + case arr.ContentTypeMovies: + if !c.Movies.Enabled() { + return nil, false, nil + } + client, err := arrclient.NewRadarrClient(c.Movies) + return client, true, err + case arr.ContentTypeSeries: + if !c.Series.Enabled() { + return nil, false, nil + } + client, err := arrclient.NewSonarrClient(c.Series) + return client, true, err + default: + return nil, false, fmt.Errorf("unsupported content type %q", contentType) + } +} diff --git a/packages/arrtrix/pkg/connector/connector.go b/packages/arrtrix/pkg/connector/connector.go index 121e94c..4be007a 100644 --- a/packages/arrtrix/pkg/connector/connector.go +++ b/packages/arrtrix/pkg/connector/connector.go @@ -10,11 +10,17 @@ import ( "maunium.net/go/mautrix/bridgev2/networkid" "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/id" + + "sneeuwvlok/packages/arrtrix/pkg/arr" + "sneeuwvlok/packages/arrtrix/pkg/arrclient" + "sneeuwvlok/packages/arrtrix/pkg/subscriptions" ) type ArrtrixConnector struct { - Bridge *bridgev2.Bridge - Config Config + Bridge *bridgev2.Bridge + Config Config + clients map[arr.ContentType]arrclient.Client + subscriptions *subscriptions.Repository } var _ bridgev2.NetworkConnector = (*ArrtrixConnector)(nil) @@ -33,6 +39,17 @@ func (s *ArrtrixConnector) GetName() bridgev2.BridgeName { func (s *ArrtrixConnector) Init(bridge *bridgev2.Bridge) { s.Bridge = bridge + s.subscriptions = subscriptions.NewRepository(bridge.DB.Database, string(bridge.ID)) + s.clients = make(map[arr.ContentType]arrclient.Client) + for _, contentType := range arr.SupportedContentTypes() { + client, ok, err := s.Config.Content.Client(contentType) + if err != nil { + panic(err) + } + if ok { + s.clients[contentType] = client + } + } } func (s *ArrtrixConnector) Start(context.Context) error { @@ -107,3 +124,12 @@ func (c *ArrtrixClient) HandleMatrixMessage(context.Context, *bridgev2.MatrixMes func (c *ArrtrixClient) GenerateTransactionID(userID id.UserID, roomID id.RoomID, eventType event.Type) networkid.RawTransactionID { return networkid.RawTransactionID("") } + +func (s *ArrtrixConnector) ContentClient(contentType arr.ContentType) (arrclient.Client, bool) { + client, ok := s.clients[contentType] + return client, ok +} + +func (s *ArrtrixConnector) Subscriptions() *subscriptions.Repository { + return s.subscriptions +} diff --git a/packages/arrtrix/pkg/connector/example-config.yaml b/packages/arrtrix/pkg/connector/example-config.yaml index 9c11ddf..a917e23 100644 --- a/packages/arrtrix/pkg/connector/example-config.yaml +++ b/packages/arrtrix/pkg/connector/example-config.yaml @@ -1,4 +1,23 @@ -# No network-specific config is required yet. -# +content: + movies: + # Radarr connection for movie management commands. + url: "" + api_key: "" + root_folder_path: "" + quality_profile_id: 0 + minimum_availability: released + search_on_add: true + + series: + # Sonarr connection for series management commands. + url: "" + api_key: "" + root_folder_path: "" + quality_profile_id: 0 + language_profile_id: 0 + season_folder: true + series_type: standard + search_on_add: true + # Arr-stack webhooks are exposed automatically on the fixed built-in path: # POST /_arrtrix/webhook diff --git a/packages/arrtrix/pkg/matrixcmd/download.go b/packages/arrtrix/pkg/matrixcmd/download.go new file mode 100644 index 0000000..6d27a1a --- /dev/null +++ b/packages/arrtrix/pkg/matrixcmd/download.go @@ -0,0 +1,222 @@ +package matrixcmd + +import ( + "fmt" + "strconv" + "strings" + + "maunium.net/go/mautrix/id" + + "sneeuwvlok/packages/arrtrix/pkg/arr" + "sneeuwvlok/packages/arrtrix/pkg/arrclient" + "sneeuwvlok/packages/arrtrix/pkg/subscriptions" +) + +type commandServiceProvider interface { + ContentClient(arr.ContentType) (arrclient.Client, bool) + Subscriptions() *subscriptions.Repository +} + +func NewDownloadHandler() Handler { + return NewHandler(Meta{ + Name: "download", + Description: "Manage monitored movies and series in Arr.", + Usage: " [...]", + }, func(ctx *Context) { + if len(ctx.Args) < 2 { + ctx.Reply("Usage: `download [...]`") + return + } + + contentType, err := arr.ParseContentType(ctx.Args[1]) + if err != nil { + ctx.Reply(err.Error()) + return + } + + client, ok := contentClient(ctx, contentType) + if !ok { + ctx.Reply("No %s client is configured yet.", contentType.Label()) + return + } + + switch strings.ToLower(ctx.Args[0]) { + case "list": + handleDownloadList(ctx, client, contentType) + case "search": + handleDownloadSearch(ctx, client, contentType) + case "add": + handleDownloadAdd(ctx, client, contentType) + case "monitor": + handleDownloadMonitor(ctx, client, contentType) + case "remove": + handleDownloadRemove(ctx, client, contentType) + default: + ctx.Reply("Unknown download subcommand `%s`.", ctx.Args[0]) + } + }) +} + +func handleDownloadList(ctx *Context, client arrclient.Client, contentType arr.ContentType) { + query := strings.TrimSpace(strings.Join(ctx.Args[2:], " ")) + items, err := client.List(ctx.Ctx, query) + if err != nil { + ctx.Reply("Failed to list %s: %v", contentType.Label(), err) + return + } + if len(items) == 0 { + if query == "" { + ctx.Reply("No monitored %s are currently tracked.", contentType.Label()) + } else { + ctx.Reply("No %s matched `%s`.", contentType.Label(), query) + } + return + } + + var builder strings.Builder + builder.WriteString(fmt.Sprintf("Tracked %s:\n", contentType.Label())) + for i, item := range items { + if i == 10 { + builder.WriteString("…\n") + break + } + builder.WriteString(fmt.Sprintf("- `%d` %s β€” monitored=%t\n", item.ID, formatManagedItem(item), item.Monitored)) + } + ctx.Reply(builder.String()) +} + +func handleDownloadSearch(ctx *Context, client arrclient.Client, contentType arr.ContentType) { + query := strings.TrimSpace(strings.Join(ctx.Args[2:], " ")) + if query == "" { + ctx.Reply("Usage: `download search %s `", contentType.Label()) + return + } + results, err := client.Search(ctx.Ctx, query) + if err != nil { + ctx.Reply("Failed to search %s: %v", contentType.Label(), err) + return + } + replyWithSearchResults(ctx, contentType, query, results) +} + +func handleDownloadAdd(ctx *Context, client arrclient.Client, contentType arr.ContentType) { + query := strings.TrimSpace(strings.Join(ctx.Args[2:], " ")) + if query == "" { + ctx.Reply("Usage: `download add %s `", contentType.Label()) + return + } + results, err := client.Search(ctx.Ctx, query) + if err != nil { + ctx.Reply("Failed to search %s: %v", contentType.Label(), err) + return + } + result, err := arrclient.PickSingleResult(results, query) + if err != nil { + replyWithSearchResults(ctx, contentType, query, results) + return + } + item, err := client.Add(ctx.Ctx, result) + if err != nil { + ctx.Reply("Failed to add %s: %v", contentType.Label(), err) + return + } + ctx.Reply("Added %s to %s with id `%d`.", formatManagedItem(*item), contentType.Label(), item.ID) +} + +func handleDownloadMonitor(ctx *Context, client arrclient.Client, contentType arr.ContentType) { + if len(ctx.Args) < 4 { + ctx.Reply("Usage: `download monitor %s `", contentType.Label()) + return + } + itemID, err := strconv.ParseInt(ctx.Args[2], 10, 64) + if err != nil { + ctx.Reply("Invalid %s id `%s`.", contentType.Label(), ctx.Args[2]) + return + } + + state, err := parseEnabled(ctx.Args[3]) + if err != nil { + ctx.Reply(err.Error()) + return + } + item, err := client.SetMonitored(ctx.Ctx, itemID, state) + if err != nil { + ctx.Reply("Failed to update %s monitoring: %v", contentType.Label(), err) + return + } + ctx.Reply("%s is now monitored=%t.", formatManagedItem(*item), item.Monitored) +} + +func handleDownloadRemove(ctx *Context, client arrclient.Client, contentType arr.ContentType) { + if len(ctx.Args) < 3 { + ctx.Reply("Usage: `download remove %s `", contentType.Label()) + return + } + itemID, err := strconv.ParseInt(ctx.Args[2], 10, 64) + if err != nil { + ctx.Reply("Invalid %s id `%s`.", contentType.Label(), ctx.Args[2]) + return + } + if err = client.Delete(ctx.Ctx, itemID); err != nil { + ctx.Reply("Failed to remove %s: %v", contentType.Label(), err) + return + } + ctx.Reply("Removed `%d` from %s.", itemID, contentType.Label()) +} + +func contentClient(ctx *Context, contentType arr.ContentType) (arrclient.Client, bool) { + provider, ok := ctx.Bridge.Network.(commandServiceProvider) + if !ok { + return nil, false + } + return provider.ContentClient(contentType) +} + +func contentSubscriptions(ctx *Context) *subscriptions.Repository { + provider, ok := ctx.Bridge.Network.(commandServiceProvider) + if !ok { + return nil + } + return provider.Subscriptions() +} + +func replyWithSearchResults(ctx *Context, contentType arr.ContentType, query string, results []arrclient.SearchResult) { + if len(results) == 0 { + ctx.Reply("No %s matched `%s`.", contentType.Label(), query) + return + } + + var builder strings.Builder + builder.WriteString(fmt.Sprintf("Search results for `%s` in %s:\n", query, contentType.Label())) + for i, result := range results { + if i == 8 { + builder.WriteString("…\n") + break + } + builder.WriteString(fmt.Sprintf("- `%d` %s\n", result.LookupID, arrclient.FormatSearchResult(result))) + } + builder.WriteString(fmt.Sprintf("\nRefine the query and rerun `download add %s ` until only one match remains.", contentType.Label())) + ctx.Reply(builder.String()) +} + +func formatManagedItem(item arrclient.ManagedItem) string { + if item.Year != 0 { + return fmt.Sprintf("%s (%d)", item.Title, item.Year) + } + return item.Title +} + +func parseEnabled(value string) (bool, error) { + switch strings.ToLower(strings.TrimSpace(value)) { + case "on", "true", "yes", "enabled": + return true, nil + case "off", "false", "no", "disabled": + return false, nil + default: + return false, fmt.Errorf("expected `on` or `off`, got `%s`", value) + } +} + +func userIDString(userID id.UserID) string { + return userID.String() +} diff --git a/packages/arrtrix/pkg/matrixcmd/help_test.go b/packages/arrtrix/pkg/matrixcmd/help_test.go index b5b325b..73fed6d 100644 --- a/packages/arrtrix/pkg/matrixcmd/help_test.go +++ b/packages/arrtrix/pkg/matrixcmd/help_test.go @@ -32,7 +32,9 @@ func TestFormatHelpManagementRoom(t *testing.T) { for _, fragment := range []string{ "prefixing commands with `!arr` is not required", + "**download** [...] - Manage monitored movies and series in Arr.", "**help** - Show this help message.", + "**subscriptions** [movies|series] [event-type|all] - Manage notification subscriptions by content type and event type.", "Extra help text.", } { if !strings.Contains(out, fragment) { diff --git a/packages/arrtrix/pkg/matrixcmd/processor.go b/packages/arrtrix/pkg/matrixcmd/processor.go index a4f15df..78915ea 100644 --- a/packages/arrtrix/pkg/matrixcmd/processor.go +++ b/packages/arrtrix/pkg/matrixcmd/processor.go @@ -87,6 +87,8 @@ func NewProcessor(bridge *bridgev2.Bridge, texts bridgeconfig.ManagementRoomText alias: make(map[string]string), } proc.Add(NewHelpHandler(proc)) + proc.Add(NewDownloadHandler()) + proc.Add(NewSubscriptionsHandler()) return proc } diff --git a/packages/arrtrix/pkg/matrixcmd/subscriptions.go b/packages/arrtrix/pkg/matrixcmd/subscriptions.go new file mode 100644 index 0000000..ed1a11f --- /dev/null +++ b/packages/arrtrix/pkg/matrixcmd/subscriptions.go @@ -0,0 +1,107 @@ +package matrixcmd + +import ( + "context" + "fmt" + "strings" + + "maunium.net/go/mautrix/id" + + "sneeuwvlok/packages/arrtrix/pkg/arr" + "sneeuwvlok/packages/arrtrix/pkg/subscriptions" +) + +func NewSubscriptionsHandler() Handler { + return NewHandler(Meta{ + Name: "subscriptions", + Aliases: []string{"subscription", "notify"}, + Description: "Manage notification subscriptions by content type and event type.", + Usage: " [movies|series] [event-type|all]", + }, func(ctx *Context) { + repo := contentSubscriptions(ctx) + if repo == nil { + ctx.Reply("Subscription storage is not available.") + return + } + if len(ctx.Args) == 0 || strings.EqualFold(ctx.Args[0], "list") { + handleSubscriptionList(ctx, repo) + return + } + if len(ctx.Args) < 3 { + ctx.Reply("Usage: `subscriptions `") + return + } + + contentType, err := arr.ParseContentType(ctx.Args[1]) + if err != nil { + ctx.Reply(err.Error()) + return + } + eventType, err := arr.ParseEventType(contentType, ctx.Args[2]) + if err != nil { + ctx.Reply(err.Error()) + return + } + + switch strings.ToLower(ctx.Args[0]) { + case "enable": + handleSubscriptionSet(ctx, repo, contentType, eventType, true) + case "disable": + handleSubscriptionSet(ctx, repo, contentType, eventType, false) + default: + ctx.Reply("Unknown subscriptions subcommand `%s`.", ctx.Args[0]) + } + }) +} + +func handleSubscriptionList(ctx *Context, repo subscriptionRepo) { + preferences, err := repo.List(ctx.Ctx, ctx.User.MXID) + if err != nil { + ctx.Reply("Failed to load subscriptions: %v", err) + return + } + + var builder strings.Builder + builder.WriteString("Current notification subscriptions:\n") + for _, contentType := range arr.SupportedContentTypes() { + builder.WriteString(fmt.Sprintf("\n**%s**\n", strings.Title(contentType.Label()))) + for _, eventType := range arr.SupportedEventTypes(contentType) { + enabled := findPreference(preferences, contentType, eventType) + builder.WriteString(fmt.Sprintf("- `%s`: %t\n", eventType, enabled)) + } + } + ctx.Reply(builder.String()) +} + +func handleSubscriptionSet(ctx *Context, repo subscriptionRepo, contentType arr.ContentType, eventType string, enabled bool) { + var err error + if eventType == "all" { + err = repo.SetAll(ctx.Ctx, ctx.User.MXID, contentType, enabled) + } else { + err = repo.Set(ctx.Ctx, ctx.User.MXID, contentType, eventType, enabled) + } + if err != nil { + ctx.Reply("Failed to update subscriptions: %v", err) + return + } + if eventType == "all" { + ctx.Reply("Set all `%s` notifications for %s to %t.", contentType.Label(), userIDString(ctx.User.MXID), enabled) + return + } + ctx.Reply("Set `%s/%s` notifications to %t.", contentType.Label(), eventType, enabled) +} + +type subscriptionRepo interface { + List(ctx context.Context, userID id.UserID) ([]subscriptions.Preference, error) + Set(ctx context.Context, userID id.UserID, contentType arr.ContentType, eventType string, enabled bool) error + SetAll(ctx context.Context, userID id.UserID, contentType arr.ContentType, enabled bool) error +} + +func findPreference(preferences []subscriptions.Preference, contentType arr.ContentType, eventType string) bool { + for _, preference := range preferences { + if preference.ContentType == contentType && preference.EventType == eventType { + return preference.Enabled + } + } + return true +} diff --git a/packages/arrtrix/pkg/runtime/main.go b/packages/arrtrix/pkg/runtime/main.go index 5352c54..c685706 100644 --- a/packages/arrtrix/pkg/runtime/main.go +++ b/packages/arrtrix/pkg/runtime/main.go @@ -34,6 +34,7 @@ import ( "sneeuwvlok/packages/arrtrix/pkg/matrixcmd" "sneeuwvlok/packages/arrtrix/pkg/observability" "sneeuwvlok/packages/arrtrix/pkg/onboarding" + "sneeuwvlok/packages/arrtrix/pkg/subscriptions" ) var configPath = flag.MakeFull("c", "config", "The path to your config file.", "config.yaml").String() @@ -305,6 +306,10 @@ func (m *Main) Init() { Msg("Initializing bridge") m.initDB() + if err = subscriptions.EnsureSchema(ctx, m.DB); err != nil { + m.Log.WithLevel(zerolog.FatalLevel).Err(err).Msg("Failed to initialize subscription schema") + os.Exit(14) + } m.Matrix = matrix.NewConnector(m.Config) m.Matrix.OnWebsocketReplaced = func() { m.TriggerStop(0) diff --git a/packages/arrtrix/pkg/subscriptions/repo.go b/packages/arrtrix/pkg/subscriptions/repo.go new file mode 100644 index 0000000..85c6b57 --- /dev/null +++ b/packages/arrtrix/pkg/subscriptions/repo.go @@ -0,0 +1,141 @@ +package subscriptions + +import ( + "context" + "fmt" + + "go.mau.fi/util/dbutil" + "maunium.net/go/mautrix/id" + + "sneeuwvlok/packages/arrtrix/pkg/arr" +) + +type Preference struct { + ContentType arr.ContentType + EventType string + Enabled bool +} + +type Repository struct { + db *dbutil.Database + bridgeID string +} + +func EnsureSchema(ctx context.Context, db *dbutil.Database) error { + _, err := db.Exec(ctx, ` + CREATE TABLE IF NOT EXISTS arrtrix_subscription ( + bridge_id TEXT NOT NULL, + user_mxid TEXT NOT NULL, + content_type TEXT NOT NULL, + event_type TEXT NOT NULL, + enabled BOOLEAN NOT NULL, + PRIMARY KEY (bridge_id, user_mxid, content_type, event_type) + ) + `) + return err +} + +func NewRepository(db *dbutil.Database, bridgeID string) *Repository { + return &Repository{db: db, bridgeID: bridgeID} +} + +func (r *Repository) EnsureDefaults(ctx context.Context, userID id.UserID) error { + var existing int + if err := r.db.QueryRow(ctx, `SELECT COUNT(*) FROM arrtrix_subscription WHERE bridge_id=$1 AND user_mxid=$2`, r.bridgeID, userID.String()).Scan(&existing); err != nil { + return err + } + if existing > 0 { + return nil + } + + for _, contentType := range arr.SupportedContentTypes() { + for _, eventType := range arr.SupportedEventTypes(contentType) { + if _, err := r.db.Exec(ctx, ` + INSERT INTO arrtrix_subscription (bridge_id, user_mxid, content_type, event_type, enabled) + VALUES ($1, $2, $3, $4, TRUE) + `, r.bridgeID, userID.String(), string(contentType), eventType); err != nil { + return err + } + } + } + return nil +} + +func (r *Repository) List(ctx context.Context, userID id.UserID) ([]Preference, error) { + if err := r.EnsureDefaults(ctx, userID); err != nil { + return nil, err + } + + rows, err := r.db.Query(ctx, ` + SELECT content_type, event_type, enabled + FROM arrtrix_subscription + WHERE bridge_id=$1 AND user_mxid=$2 + ORDER BY content_type, event_type + `, r.bridgeID, userID.String()) + if err != nil { + return nil, err + } + defer rows.Close() + + var preferences []Preference + for rows.Next() { + var contentType string + var preference Preference + if err = rows.Scan(&contentType, &preference.EventType, &preference.Enabled); err != nil { + return nil, err + } + preference.ContentType = arr.ContentType(contentType) + preferences = append(preferences, preference) + } + if err = rows.Err(); err != nil { + return nil, err + } + return preferences, nil +} + +func (r *Repository) Set(ctx context.Context, userID id.UserID, contentType arr.ContentType, eventType string, enabled bool) error { + if err := r.EnsureDefaults(ctx, userID); err != nil { + return err + } + if _, err := r.db.Exec(ctx, ` + INSERT INTO arrtrix_subscription (bridge_id, user_mxid, content_type, event_type, enabled) + VALUES ($1, $2, $3, $4, $5) + ON CONFLICT (bridge_id, user_mxid, content_type, event_type) + DO UPDATE SET enabled=excluded.enabled + `, r.bridgeID, userID.String(), string(contentType), eventType, enabled); err != nil { + return err + } + return nil +} + +func (r *Repository) SetAll(ctx context.Context, userID id.UserID, contentType arr.ContentType, enabled bool) error { + if err := r.EnsureDefaults(ctx, userID); err != nil { + return err + } + for _, eventType := range arr.SupportedEventTypes(contentType) { + if err := r.Set(ctx, userID, contentType, eventType, enabled); err != nil { + return err + } + } + return nil +} + +func (r *Repository) Allows(ctx context.Context, userID id.UserID, contentType arr.ContentType, eventType string) (bool, error) { + if !arr.SupportsEventType(contentType, eventType) { + return true, nil + } + if err := r.EnsureDefaults(ctx, userID); err != nil { + return false, err + } + + var enabled bool + err := r.db.QueryRow(ctx, ` + SELECT enabled + FROM arrtrix_subscription + WHERE bridge_id=$1 AND user_mxid=$2 AND content_type=$3 AND event_type=$4 + `, r.bridgeID, userID.String(), string(contentType), eventType).Scan(&enabled) + if err != nil { + return false, fmt.Errorf("query subscription: %w", err) + } + return enabled, nil +} diff --git a/packages/arrtrix/pkg/webhook/arr.go b/packages/arrtrix/pkg/webhook/arr.go index eb7540c..5446825 100644 --- a/packages/arrtrix/pkg/webhook/arr.go +++ b/packages/arrtrix/pkg/webhook/arr.go @@ -17,6 +17,7 @@ import ( "maunium.net/go/mautrix/format" "maunium.net/go/mautrix/id" + "sneeuwvlok/packages/arrtrix/pkg/arr" "sneeuwvlok/packages/arrtrix/pkg/observability" ) @@ -28,10 +29,13 @@ var ( ) type payload struct { - EventType string `json:"eventType"` - Movie *movie `json:"movie"` - MovieFile *movieFile `json:"movieFile"` - IsUpgrade bool `json:"isUpgrade"` + EventType string `json:"eventType"` + Movie *movie `json:"movie"` + MovieFile *movieFile `json:"movieFile"` + Series *series `json:"series"` + Episodes []episode `json:"episodes"` + EpisodeFile *episodeFile `json:"episodeFile"` + IsUpgrade bool `json:"isUpgrade"` } type movie struct { @@ -49,26 +53,55 @@ type movieFile struct { ReleaseGroup string `json:"releaseGroup"` } +type series struct { + Title string `json:"title"` + Year int `json:"year"` + Path string `json:"path"` +} + +type episode struct { + SeasonNumber int `json:"seasonNumber"` + EpisodeNumber int `json:"episodeNumber"` + Title string `json:"title"` +} + +type episodeFile struct { + Quality string `json:"quality"` + RelativePath string `json:"relativePath"` + SceneName string `json:"sceneName"` +} + +type managementTarget struct { + UserID id.UserID + RoomID id.RoomID +} + type roomResolver interface { - ResolveManagementRoom(context.Context) (id.RoomID, error) + ResolveManagementRoom(context.Context) (managementTarget, error) } type noticeSender interface { SendNotice(context.Context, id.RoomID, string) error } -type ArrHandler struct { - resolver roomResolver - sender noticeSender +type SubscriptionFilter interface { + Allows(context.Context, id.UserID, arr.ContentType, string) (bool, error) } -func MountArr(router *http.ServeMux, bridge *bridgev2.Bridge) error { +type ArrHandler struct { + resolver roomResolver + sender noticeSender + subscriptions SubscriptionFilter +} + +func MountArr(router *http.ServeMux, bridge *bridgev2.Bridge, subscriptions SubscriptionFilter) error { if bridge == nil { return fmt.Errorf("bridge is not initialized") } handler := &ArrHandler{ - resolver: bridgeRoomResolver{bridge: bridge}, - sender: bridgeNoticeSender{bridge: bridge}, + resolver: bridgeRoomResolver{bridge: bridge}, + sender: bridgeNoticeSender{bridge: bridge}, + subscriptions: subscriptions, } router.Handle(fmt.Sprintf("POST %s", ArrWebhookPath), handler) return nil @@ -109,7 +142,7 @@ func (h *ArrHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { attribute.String("http.route", ArrWebhookPath), ) - roomID, err := h.resolver.ResolveManagementRoom(ctx) + target, err := h.resolver.ResolveManagementRoom(ctx) if err != nil { statusCode = http.StatusInternalServerError outcome = "resolve_failed" @@ -123,7 +156,26 @@ func (h *ArrHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - if err = h.sender.SendNotice(ctx, roomID, renderNotice(body)); err != nil { + contentType, ok := body.ContentType() + if ok && h.subscriptions != nil { + allowed, filterErr := h.subscriptions.Allows(ctx, target.UserID, contentType, body.EventType) + if filterErr != nil { + statusCode = http.StatusInternalServerError + outcome = "subscription_check_failed" + span.RecordError(filterErr) + span.SetStatus(codes.Error, filterErr.Error()) + http.Error(w, "failed to evaluate subscriptions", statusCode) + return + } + if !allowed { + outcome = "filtered" + span.SetStatus(codes.Ok, "filtered") + w.WriteHeader(statusCode) + return + } + } + + if err = h.sender.SendNotice(ctx, target.RoomID, renderNotice(body)); err != nil { statusCode = http.StatusBadGateway outcome = "delivery_failed" span.RecordError(err) @@ -140,7 +192,7 @@ type bridgeRoomResolver struct { bridge *bridgev2.Bridge } -func (r bridgeRoomResolver) ResolveManagementRoom(ctx context.Context) (id.RoomID, error) { +func (r bridgeRoomResolver) ResolveManagementRoom(ctx context.Context) (managementTarget, error) { ctx, span := observability.StartSpan(ctx, "arrtrix.webhook.resolve_management_room") defer span.End() @@ -148,42 +200,45 @@ func (r bridgeRoomResolver) ResolveManagementRoom(ctx context.Context) (id.RoomI if err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) - return "", fmt.Errorf("failed to query management rooms: %w", err) + return managementTarget{}, fmt.Errorf("failed to query management rooms: %w", err) } defer rows.Close() - var roomID id.RoomID + var target managementTarget var owners []id.UserID for rows.Next() { var mxid, managementRoom string if err = rows.Scan(&mxid, &managementRoom); err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) - return "", fmt.Errorf("failed to scan management room: %w", err) + return managementTarget{}, fmt.Errorf("failed to scan management room: %w", err) } owners = append(owners, id.UserID(mxid)) - if roomID == "" { - roomID = id.RoomID(managementRoom) + if target.RoomID == "" { + target = managementTarget{ + UserID: id.UserID(mxid), + RoomID: id.RoomID(managementRoom), + } } } if err = rows.Err(); err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) - return "", fmt.Errorf("failed to iterate management rooms: %w", err) + return managementTarget{}, fmt.Errorf("failed to iterate management rooms: %w", err) } switch len(owners) { case 0: span.SetStatus(codes.Error, ErrNoManagementRoom.Error()) - return "", ErrNoManagementRoom + return managementTarget{}, ErrNoManagementRoom case 1: span.SetAttributes(attribute.Int("arrtrix.management_room.count", 1)) span.SetStatus(codes.Ok, "") - return roomID, nil + return target, nil default: span.SetAttributes(attribute.Int("arrtrix.management_room.count", len(owners))) span.SetStatus(codes.Error, ErrAmbiguousManagementRoom.Error()) - return "", fmt.Errorf("%w: %s", ErrAmbiguousManagementRoom, strings.Join(convertUserIDs(owners), ", ")) + return managementTarget{}, fmt.Errorf("%w: %s", ErrAmbiguousManagementRoom, strings.Join(convertUserIDs(owners), ", ")) } } @@ -213,30 +268,48 @@ func (s bridgeNoticeSender) SendNotice(ctx context.Context, roomID id.RoomID, ma } func renderNotice(body payload) string { - title := "Arr" - if body.Movie != nil { - title = body.Movie.Title + lines := []string{fmt.Sprintf("**Arr %s**", body.EventType)} + + switch contentType, ok := body.ContentType(); { + case ok && contentType == arr.ContentTypeMovies: + title := body.Movie.Title if body.Movie.Year != 0 { title = fmt.Sprintf("%s (%d)", title, body.Movie.Year) } + lines = append(lines, fmt.Sprintf("Movie: %s", title)) + if body.MovieFile != nil && body.MovieFile.Quality != "" { + lines = append(lines, fmt.Sprintf("Quality: %s", body.MovieFile.Quality)) + } + if body.MovieFile != nil && body.MovieFile.RelativePath != "" { + lines = append(lines, fmt.Sprintf("File: `%s`", body.MovieFile.RelativePath)) + } + if body.EventType == "Download" { + lines = append(lines, fmt.Sprintf("Upgrade: %t", body.IsUpgrade)) + } + if body.Movie.ImdbID != "" { + lines = append(lines, fmt.Sprintf("IMDb: `%s`", body.Movie.ImdbID)) + } + case ok && contentType == arr.ContentTypeSeries: + title := body.Series.Title + if body.Series.Year != 0 { + title = fmt.Sprintf("%s (%d)", title, body.Series.Year) + } + lines = append(lines, fmt.Sprintf("Series: %s", title)) + if len(body.Episodes) > 0 { + lines = append(lines, fmt.Sprintf("Episodes: %s", renderEpisodes(body.Episodes))) + } + if body.EpisodeFile != nil && body.EpisodeFile.Quality != "" { + lines = append(lines, fmt.Sprintf("Quality: %s", body.EpisodeFile.Quality)) + } + if body.EpisodeFile != nil && body.EpisodeFile.RelativePath != "" { + lines = append(lines, fmt.Sprintf("File: `%s`", body.EpisodeFile.RelativePath)) + } + default: + if body.EventType != "Test" { + lines = append(lines, "Payload received.") + } } - lines := []string{fmt.Sprintf("**Arr %s**", body.EventType)} - if title != "Arr" { - lines = append(lines, fmt.Sprintf("Movie: %s", title)) - } - if body.MovieFile != nil && body.MovieFile.Quality != "" { - lines = append(lines, fmt.Sprintf("Quality: %s", body.MovieFile.Quality)) - } - if body.MovieFile != nil && body.MovieFile.RelativePath != "" { - lines = append(lines, fmt.Sprintf("File: `%s`", body.MovieFile.RelativePath)) - } - if body.EventType == "Download" { - lines = append(lines, fmt.Sprintf("Upgrade: %t", body.IsUpgrade)) - } - if body.Movie != nil && body.Movie.ImdbID != "" { - lines = append(lines, fmt.Sprintf("IMDb: `%s`", body.Movie.ImdbID)) - } return strings.Join(lines, "\n") } @@ -251,3 +324,26 @@ func convertUserIDs(users []id.UserID) []string { var _ roomResolver = bridgeRoomResolver{} var _ noticeSender = bridgeNoticeSender{} var _ http.Handler = (*ArrHandler)(nil) + +func (p payload) ContentType() (arr.ContentType, bool) { + switch { + case p.Movie != nil: + return arr.ContentTypeMovies, true + case p.Series != nil: + return arr.ContentTypeSeries, true + default: + return "", false + } +} + +func renderEpisodes(episodes []episode) string { + parts := make([]string, 0, len(episodes)) + for _, item := range episodes { + if item.Title != "" { + parts = append(parts, fmt.Sprintf("S%02dE%02d %s", item.SeasonNumber, item.EpisodeNumber, item.Title)) + continue + } + parts = append(parts, fmt.Sprintf("S%02dE%02d", item.SeasonNumber, item.EpisodeNumber)) + } + return strings.Join(parts, ", ") +} diff --git a/packages/arrtrix/pkg/webhook/arr_test.go b/packages/arrtrix/pkg/webhook/arr_test.go index b7ac511..246df72 100644 --- a/packages/arrtrix/pkg/webhook/arr_test.go +++ b/packages/arrtrix/pkg/webhook/arr_test.go @@ -12,12 +12,12 @@ import ( ) type stubRoomResolver struct { - roomID id.RoomID + target managementTarget err error } -func (s stubRoomResolver) ResolveManagementRoom(context.Context) (id.RoomID, error) { - return s.roomID, s.err +func (s stubRoomResolver) ResolveManagementRoom(context.Context) (managementTarget, error) { + return s.target, s.err } type stubNoticeSender struct { @@ -34,7 +34,7 @@ func (s *stubNoticeSender) SendNotice(_ context.Context, roomID id.RoomID, messa func TestMountArrRequiresBridge(t *testing.T) { router := http.NewServeMux() - if err := MountArr(router, nil); err == nil { + if err := MountArr(router, nil, nil); err == nil { t.Fatal("expected nil bridge to fail") } } @@ -42,7 +42,7 @@ func TestMountArrRequiresBridge(t *testing.T) { func TestArrHandlerDeliversNotice(t *testing.T) { sender := &stubNoticeSender{} handler := &ArrHandler{ - resolver: stubRoomResolver{roomID: "!room:test"}, + resolver: stubRoomResolver{target: managementTarget{UserID: "@user:test", RoomID: "!room:test"}}, sender: sender, } @@ -85,7 +85,7 @@ func TestRenderNoticeForTestEvent(t *testing.T) { func TestArrHandlerReturnsBadGatewayOnSendFailure(t *testing.T) { handler := &ArrHandler{ - resolver: stubRoomResolver{roomID: "!room:test"}, + resolver: stubRoomResolver{target: managementTarget{UserID: "@user:test", RoomID: "!room:test"}}, sender: &stubNoticeSender{err: errors.New("send failed")}, } @@ -100,7 +100,7 @@ func TestArrHandlerReturnsBadGatewayOnSendFailure(t *testing.T) { func TestArrHandlerRejectsMissingEventType(t *testing.T) { handler := &ArrHandler{ - resolver: stubRoomResolver{roomID: "!room:test"}, + resolver: stubRoomResolver{target: managementTarget{UserID: "@user:test", RoomID: "!room:test"}}, sender: &stubNoticeSender{}, } diff --git a/systems/x86_64-linux/ulmo/default.nix b/systems/x86_64-linux/ulmo/default.nix index 57f57d3..18c5751 100644 --- a/systems/x86_64-linux/ulmo/default.nix +++ b/systems/x86_64-linux/ulmo/default.nix @@ -146,7 +146,7 @@ }; grafana = { - redirectUris = ["http://localhost:9001/login/generic_oauth"]; + redirectUris = ["http://localhost:9100/login/generic_oauth"]; grantTypes = ["authorizationCode"]; responseTypes = ["code"]; }; From be2843ca8026046671318f9e9f7134365cd70e79 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Thu, 16 Apr 2026 11:00:38 +0200 Subject: [PATCH 106/108] . --- modules/nixos/services/media/default.nix | 2 +- .../nixos/services/media/mydia/default.nix | 4 +-- .../nixos/services/media/servarr/default.nix | 10 +++--- .../services/observability/alloy/default.nix | 8 ++--- .../observability/grafana/default.nix | 8 ++--- .../services/observability/loki/default.nix | 4 +-- .../observability/prometheus/default.nix | 12 +++---- .../observability/promtail/default.nix | 6 ++-- .../services/observability/tempo/default.nix | 8 ++--- .../observability/uptime-kuma/default.nix | 4 +-- .../nixos/temp/services/arrtrix/default.nix | 20 ++++++++++++ packages/arrtrix/pkg/arr/catalog_test.go | 23 ++++++++++++++ packages/arrtrix/pkg/connector/config_test.go | 23 ++++++++++++++ packages/arrtrix/pkg/matrixcmd/help_test.go | 2 ++ packages/arrtrix/pkg/webhook/arr_test.go | 31 +++++++++++++++++++ shells/default/default.nix | 1 + systems/x86_64-linux/ulmo/default.nix | 12 +++---- .../ulmo/lidarr/api_key/machines/ulmo | 1 + .../ulmo/lidarr/api_key/users/chris | 1 + .../ulmo/lidarr/config.env/machines/ulmo | 1 + .../ulmo/lidarr/config.env/users/chris | 1 + .../ulmo/postgresql/.pgpass/machines/ulmo | 1 + .../ulmo/postgresql/.pgpass/users/chris | 1 + .../ulmo/postgresql/lidarr_hash/users/chris | 1 + .../postgresql/lidarr_password/users/chris | 1 + .../ulmo/postgresql/prowlarr_hash/users/chris | 1 + .../postgresql/prowlarr_password/users/chris | 1 + .../ulmo/postgresql/radarr_hash/users/chris | 1 + .../postgresql/radarr_password/users/chris | 1 + .../ulmo/postgresql/server.crt/machines/ulmo | 1 + .../ulmo/postgresql/server.crt/users/chris | 1 + .../ulmo/postgresql/server.key/machines/ulmo | 1 + .../ulmo/postgresql/server.key/users/chris | 1 + .../ulmo/postgresql/sonarr_hash/users/chris | 1 + .../postgresql/sonarr_password/users/chris | 1 + .../ulmo/prowlarr/api_key/machines/ulmo | 1 + .../ulmo/prowlarr/api_key/users/chris | 1 + .../ulmo/prowlarr/config.env/machines/ulmo | 1 + .../ulmo/prowlarr/config.env/users/chris | 1 + .../ulmo/qbittorrent/password/machines/ulmo | 1 + .../ulmo/qbittorrent/password/users/chris | 1 + .../qbittorrent/password_hash/machines/ulmo | 1 + .../qbittorrent/password_hash/users/chris | 1 + .../qBittorrent.conf/machines/ulmo | 1 + .../qbittorrent/qBittorrent.conf/users/chris | 1 + .../ulmo/radarr/api_key/machines/ulmo | 1 + .../ulmo/radarr/api_key/users/chris | 1 + .../ulmo/radarr/config.env/machines/ulmo | 1 + .../ulmo/radarr/config.env/users/chris | 1 + .../ulmo/sabnzbd/api_key/machines/ulmo | 1 + .../ulmo/sabnzbd/api_key/users/chris | 1 + .../ulmo/sabnzbd/config.ini/machines/ulmo | 1 + .../ulmo/sabnzbd/config.ini/users/chris | 1 + .../ulmo/sabnzbd/nzb_key/machines/ulmo | 1 + .../ulmo/sabnzbd/nzb_key/users/chris | 1 + .../ulmo/sabnzbd/password/machines/ulmo | 1 + .../ulmo/sabnzbd/password/users/chris | 1 + .../ulmo/sabnzbd/username/machines/ulmo | 1 + .../ulmo/sabnzbd/username/users/chris | 1 + .../ulmo/servarr/config.tfvars/machines/ulmo | 1 + .../ulmo/servarr/config.tfvars/users/chris | 1 + .../ulmo/sonarr/api_key/machines/ulmo | 1 + .../ulmo/sonarr/api_key/users/chris | 1 + .../ulmo/sonarr/config.env/machines/ulmo | 1 + .../ulmo/sonarr/config.env/users/chris | 1 + 65 files changed, 187 insertions(+), 39 deletions(-) create mode 100644 packages/arrtrix/pkg/arr/catalog_test.go create mode 100644 packages/arrtrix/pkg/connector/config_test.go create mode 120000 vars/per-machine/ulmo/lidarr/api_key/machines/ulmo create mode 120000 vars/per-machine/ulmo/lidarr/api_key/users/chris create mode 120000 vars/per-machine/ulmo/lidarr/config.env/machines/ulmo create mode 120000 vars/per-machine/ulmo/lidarr/config.env/users/chris create mode 120000 vars/per-machine/ulmo/postgresql/.pgpass/machines/ulmo create mode 120000 vars/per-machine/ulmo/postgresql/.pgpass/users/chris create mode 120000 vars/per-machine/ulmo/postgresql/lidarr_hash/users/chris create mode 120000 vars/per-machine/ulmo/postgresql/lidarr_password/users/chris create mode 120000 vars/per-machine/ulmo/postgresql/prowlarr_hash/users/chris create mode 120000 vars/per-machine/ulmo/postgresql/prowlarr_password/users/chris create mode 120000 vars/per-machine/ulmo/postgresql/radarr_hash/users/chris create mode 120000 vars/per-machine/ulmo/postgresql/radarr_password/users/chris create mode 120000 vars/per-machine/ulmo/postgresql/server.crt/machines/ulmo create mode 120000 vars/per-machine/ulmo/postgresql/server.crt/users/chris create mode 120000 vars/per-machine/ulmo/postgresql/server.key/machines/ulmo create mode 120000 vars/per-machine/ulmo/postgresql/server.key/users/chris create mode 120000 vars/per-machine/ulmo/postgresql/sonarr_hash/users/chris create mode 120000 vars/per-machine/ulmo/postgresql/sonarr_password/users/chris create mode 120000 vars/per-machine/ulmo/prowlarr/api_key/machines/ulmo create mode 120000 vars/per-machine/ulmo/prowlarr/api_key/users/chris create mode 120000 vars/per-machine/ulmo/prowlarr/config.env/machines/ulmo create mode 120000 vars/per-machine/ulmo/prowlarr/config.env/users/chris create mode 120000 vars/per-machine/ulmo/qbittorrent/password/machines/ulmo create mode 120000 vars/per-machine/ulmo/qbittorrent/password/users/chris create mode 120000 vars/per-machine/ulmo/qbittorrent/password_hash/machines/ulmo create mode 120000 vars/per-machine/ulmo/qbittorrent/password_hash/users/chris create mode 120000 vars/per-machine/ulmo/qbittorrent/qBittorrent.conf/machines/ulmo create mode 120000 vars/per-machine/ulmo/qbittorrent/qBittorrent.conf/users/chris create mode 120000 vars/per-machine/ulmo/radarr/api_key/machines/ulmo create mode 120000 vars/per-machine/ulmo/radarr/api_key/users/chris create mode 120000 vars/per-machine/ulmo/radarr/config.env/machines/ulmo create mode 120000 vars/per-machine/ulmo/radarr/config.env/users/chris create mode 120000 vars/per-machine/ulmo/sabnzbd/api_key/machines/ulmo create mode 120000 vars/per-machine/ulmo/sabnzbd/api_key/users/chris create mode 120000 vars/per-machine/ulmo/sabnzbd/config.ini/machines/ulmo create mode 120000 vars/per-machine/ulmo/sabnzbd/config.ini/users/chris create mode 120000 vars/per-machine/ulmo/sabnzbd/nzb_key/machines/ulmo create mode 120000 vars/per-machine/ulmo/sabnzbd/nzb_key/users/chris create mode 120000 vars/per-machine/ulmo/sabnzbd/password/machines/ulmo create mode 120000 vars/per-machine/ulmo/sabnzbd/password/users/chris create mode 120000 vars/per-machine/ulmo/sabnzbd/username/machines/ulmo create mode 120000 vars/per-machine/ulmo/sabnzbd/username/users/chris create mode 120000 vars/per-machine/ulmo/servarr/config.tfvars/machines/ulmo create mode 120000 vars/per-machine/ulmo/servarr/config.tfvars/users/chris create mode 120000 vars/per-machine/ulmo/sonarr/api_key/machines/ulmo create mode 120000 vars/per-machine/ulmo/sonarr/api_key/users/chris create mode 120000 vars/per-machine/ulmo/sonarr/config.env/machines/ulmo create mode 120000 vars/per-machine/ulmo/sonarr/config.env/users/chris diff --git a/modules/nixos/services/media/default.nix b/modules/nixos/services/media/default.nix index c10a08e..900eee4 100644 --- a/modules/nixos/services/media/default.nix +++ b/modules/nixos/services/media/default.nix @@ -64,7 +64,7 @@ in { openFirewall = true; user = cfg.user; group = cfg.group; - listenPort = 2005; + listenPort = 2050; }; postgresql = { diff --git a/modules/nixos/services/media/mydia/default.nix b/modules/nixos/services/media/mydia/default.nix index 7e082a3..9044c2e 100644 --- a/modules/nixos/services/media/mydia/default.nix +++ b/modules/nixos/services/media/mydia/default.nix @@ -22,7 +22,7 @@ in { services.mydia = { enable = true; - port = 2010; + port = 2100; listenAddress = "0.0.0.0"; openFirewall = true; @@ -54,7 +54,7 @@ in { qbittorrent = { type = "qbittorrent"; host = "localhost"; - port = 2008; + port = 2080; username = "admin"; passwordFile = config.sops.secrets."mydia/qbittorrent_password".path; useSsl = false; diff --git a/modules/nixos/services/media/servarr/default.nix b/modules/nixos/services/media/servarr/default.nix index 47461ef..18932b1 100644 --- a/modules/nixos/services/media/servarr/default.nix +++ b/modules/nixos/services/media/servarr/default.nix @@ -79,7 +79,7 @@ in { qbittorrent = { enable = true; openFirewall = true; - webuiPort = 2008; + webuiPort = 2080; serverConfig = lib.mkForce {}; user = "qbittorrent"; @@ -100,7 +100,7 @@ in { settings = { misc = { host = "0.0.0.0"; - port = 2009; + port = 2090; host_whitelist = "${config.networking.hostName}"; permissions = "770"; @@ -126,7 +126,7 @@ in { flaresolverr = { enable = true; openFirewall = true; - port = 2007; + port = 2070; }; postgresql = let @@ -239,7 +239,7 @@ in { username = "admin"; password = lib.tfRef "var.qbittorrent_api_key"; url_base = "/"; - port = 2008; + port = 2080; }; }; @@ -251,7 +251,7 @@ in { host = "localhost"; api_key = lib.tfRef "var.sabnzbd_api_key"; url_base = "/"; - port = 2009; + port = 2090; }; }; } diff --git a/modules/nixos/services/observability/alloy/default.nix b/modules/nixos/services/observability/alloy/default.nix index 4b6d787..c6f5cac 100644 --- a/modules/nixos/services/observability/alloy/default.nix +++ b/modules/nixos/services/observability/alloy/default.nix @@ -5,10 +5,10 @@ let cfg = config.${namespace}.services.observability.alloy; - httpPort = 9700; - otlpGrpcPort = 9701; - otlpHttpPort = 9702; - tempoOtlpGrpcPort = 9602; + httpPort = 9070; + otlpGrpcPort = 9071; + otlpHttpPort = 9072; + tempoOtlpGrpcPort = 9062; in { options.${namespace}.services.observability.alloy = { diff --git a/modules/nixos/services/observability/grafana/default.nix b/modules/nixos/services/observability/grafana/default.nix index 05fb1da..d9308c0 100644 --- a/modules/nixos/services/observability/grafana/default.nix +++ b/modules/nixos/services/observability/grafana/default.nix @@ -25,7 +25,7 @@ in { settings = { server = { - http_port = 9100; + http_port = 9010; http_addr = "0.0.0.0"; domain = "ulmo"; }; @@ -106,7 +106,7 @@ in { name = "Prometheus"; uid = "prometheus"; type = "prometheus"; - url = "http://localhost:9200"; + url = "http://localhost:9020"; isDefault = true; editable = false; } @@ -115,7 +115,7 @@ in { name = "Loki"; uid = "loki"; type = "loki"; - url = "http://localhost:9300"; + url = "http://localhost:9030"; editable = false; } @@ -123,7 +123,7 @@ in { name = "Tempo"; uid = "tempo"; type = "tempo"; - url = "http://localhost:9600"; + url = "http://localhost:9060"; editable = false; jsonData = { nodeGraph.enabled = true; diff --git a/modules/nixos/services/observability/loki/default.nix b/modules/nixos/services/observability/loki/default.nix index e99448e..bab5b3f 100644 --- a/modules/nixos/services/observability/loki/default.nix +++ b/modules/nixos/services/observability/loki/default.nix @@ -17,7 +17,7 @@ in auth_enabled = false; server = { - http_listen_port = 9300; + http_listen_port = 9030; }; common = { @@ -44,6 +44,6 @@ in }; }; - networking.firewall.allowedTCPPorts = [ 9300 ]; + networking.firewall.allowedTCPPorts = [ 9030 ]; }; } diff --git a/modules/nixos/services/observability/prometheus/default.nix b/modules/nixos/services/observability/prometheus/default.nix index fc09e01..c092286 100644 --- a/modules/nixos/services/observability/prometheus/default.nix +++ b/modules/nixos/services/observability/prometheus/default.nix @@ -13,7 +13,7 @@ in config = mkIf cfg.enable { services.prometheus = { enable = true; - port = 9200; + port = 9020; extraFlags = optionals config.${namespace}.services.observability.alloy.enable [ "--web.enable-remote-write-receiver" ]; @@ -24,7 +24,7 @@ in { job_name = "prometheus"; static_configs = [ - { targets = [ "localhost:9200" ]; } + { targets = [ "localhost:9020" ]; } ]; } @@ -39,7 +39,7 @@ in { job_name = "alloy"; static_configs = [ - { targets = [ "localhost:9700" ]; } + { targets = [ "localhost:9070" ]; } ]; } ] @@ -47,7 +47,7 @@ in { job_name = "tempo"; static_configs = [ - { targets = [ "localhost:9600" ]; } + { targets = [ "localhost:9060" ]; } ]; } ]; @@ -55,13 +55,13 @@ in exporters = { node = { enable = true; - port = 9201; + port = 9021; enabledCollectors = [ "systemd" ]; openFirewall = true; }; }; }; - networking.firewall.allowedTCPPorts = [ 9200 ]; + networking.firewall.allowedTCPPorts = [ 9020 ]; }; } diff --git a/modules/nixos/services/observability/promtail/default.nix b/modules/nixos/services/observability/promtail/default.nix index 40a1b87..b852f1f 100644 --- a/modules/nixos/services/observability/promtail/default.nix +++ b/modules/nixos/services/observability/promtail/default.nix @@ -25,7 +25,7 @@ in { configuration = { server = { - http_listen_port = 9400; + http_listen_port = 9040; grpc_listen_port = 0; }; @@ -35,7 +35,7 @@ in { clients = [ { - url = "http://[::1]:9300/loki/api/v1/push"; + url = "http://[::1]:9030/loki/api/v1/push"; } ]; @@ -60,6 +60,6 @@ in { }; }; - networking.firewall.allowedTCPPorts = [9400]; + networking.firewall.allowedTCPPorts = [9040]; }; } diff --git a/modules/nixos/services/observability/tempo/default.nix b/modules/nixos/services/observability/tempo/default.nix index 9a6bd89..dd0602c 100644 --- a/modules/nixos/services/observability/tempo/default.nix +++ b/modules/nixos/services/observability/tempo/default.nix @@ -4,10 +4,10 @@ let cfg = config.${namespace}.services.observability.tempo; - httpPort = 9600; - grpcPort = 9601; - otlpGrpcPort = 9602; - otlpHttpPort = 9603; + httpPort = 9060; + grpcPort = 9061; + otlpGrpcPort = 9062; + otlpHttpPort = 9063; in { options.${namespace}.services.observability.tempo = { diff --git a/modules/nixos/services/observability/uptime-kuma/default.nix b/modules/nixos/services/observability/uptime-kuma/default.nix index f4dcde4..af0cfa8 100644 --- a/modules/nixos/services/observability/uptime-kuma/default.nix +++ b/modules/nixos/services/observability/uptime-kuma/default.nix @@ -15,11 +15,11 @@ in enable = true; settings = { - PORT = toString 9500; + PORT = toString 9050; HOST = "0.0.0.0"; }; }; - networking.firewall.allowedTCPPorts = [ 9500 ]; + networking.firewall.allowedTCPPorts = [ 9050 ]; }; } diff --git a/modules/nixos/temp/services/arrtrix/default.nix b/modules/nixos/temp/services/arrtrix/default.nix index b8c7457..0e0d5c8 100644 --- a/modules/nixos/temp/services/arrtrix/default.nix +++ b/modules/nixos/temp/services/arrtrix/default.nix @@ -53,6 +53,26 @@ service_name = "arrtrix"; resource_attributes = {}; }; + network.content = { + movies = { + url = ""; + api_key = ""; + root_folder_path = ""; + quality_profile_id = 0; + minimum_availability = "released"; + search_on_add = true; + }; + series = { + url = ""; + api_key = ""; + root_folder_path = ""; + quality_profile_id = 0; + language_profile_id = 0; + season_folder = true; + series_type = "standard"; + search_on_add = true; + }; + }; }; in { options.services.arrtrix = { diff --git a/packages/arrtrix/pkg/arr/catalog_test.go b/packages/arrtrix/pkg/arr/catalog_test.go new file mode 100644 index 0000000..e3c2784 --- /dev/null +++ b/packages/arrtrix/pkg/arr/catalog_test.go @@ -0,0 +1,23 @@ +package arr + +import "testing" + +func TestParseContentType(t *testing.T) { + contentType, err := ParseContentType("Movies") + if err != nil { + t.Fatalf("ParseContentType returned error: %v", err) + } + if contentType != ContentTypeMovies { + t.Fatalf("expected movies content type, got %q", contentType) + } +} + +func TestParseEventType(t *testing.T) { + eventType, err := ParseEventType(ContentTypeSeries, "download") + if err != nil { + t.Fatalf("ParseEventType returned error: %v", err) + } + if eventType != "Download" { + t.Fatalf("expected Download event type, got %q", eventType) + } +} diff --git a/packages/arrtrix/pkg/connector/config_test.go b/packages/arrtrix/pkg/connector/config_test.go new file mode 100644 index 0000000..9516e37 --- /dev/null +++ b/packages/arrtrix/pkg/connector/config_test.go @@ -0,0 +1,23 @@ +package connector + +import "testing" + +func TestValidateConfigRejectsPartialMoviesConfig(t *testing.T) { + conn := &ArrtrixConnector{ + Config: Config{ + Content: ContentConfig{}, + }, + } + conn.Config.Content.Movies.URL = "http://radarr.test" + + if err := conn.ValidateConfig(); err == nil { + t.Fatal("expected partial movies config to fail validation") + } +} + +func TestValidateConfigAllowsEmptyContentConfig(t *testing.T) { + conn := &ArrtrixConnector{} + if err := conn.ValidateConfig(); err != nil { + t.Fatalf("ValidateConfig returned error: %v", err) + } +} diff --git a/packages/arrtrix/pkg/matrixcmd/help_test.go b/packages/arrtrix/pkg/matrixcmd/help_test.go index 73fed6d..817f7ed 100644 --- a/packages/arrtrix/pkg/matrixcmd/help_test.go +++ b/packages/arrtrix/pkg/matrixcmd/help_test.go @@ -18,6 +18,8 @@ func TestFormatHelpManagementRoom(t *testing.T) { alias: make(map[string]string), } proc.Add(NewHelpHandler(proc)) + proc.Add(NewDownloadHandler()) + proc.Add(NewSubscriptionsHandler()) out := formatHelp(proc, &Context{ Bridge: &bridgev2.Bridge{ diff --git a/packages/arrtrix/pkg/webhook/arr_test.go b/packages/arrtrix/pkg/webhook/arr_test.go index 246df72..e7e89f6 100644 --- a/packages/arrtrix/pkg/webhook/arr_test.go +++ b/packages/arrtrix/pkg/webhook/arr_test.go @@ -9,6 +9,8 @@ import ( "testing" "maunium.net/go/mautrix/id" + + "sneeuwvlok/packages/arrtrix/pkg/arr" ) type stubRoomResolver struct { @@ -26,12 +28,21 @@ type stubNoticeSender struct { err error } +type stubSubscriptionFilter struct { + allowed bool + err error +} + func (s *stubNoticeSender) SendNotice(_ context.Context, roomID id.RoomID, message string) error { s.roomID = roomID s.message = message return s.err } +func (s stubSubscriptionFilter) Allows(context.Context, id.UserID, arr.ContentType, string) (bool, error) { + return s.allowed, s.err +} + func TestMountArrRequiresBridge(t *testing.T) { router := http.NewServeMux() if err := MountArr(router, nil, nil); err == nil { @@ -112,3 +123,23 @@ func TestArrHandlerRejectsMissingEventType(t *testing.T) { t.Fatalf("expected bad request status, got %d", rec.Code) } } + +func TestArrHandlerFiltersDisabledSubscriptions(t *testing.T) { + sender := &stubNoticeSender{} + handler := &ArrHandler{ + resolver: stubRoomResolver{target: managementTarget{UserID: "@user:test", RoomID: "!room:test"}}, + sender: sender, + subscriptions: stubSubscriptionFilter{allowed: false}, + } + + req := httptest.NewRequest(http.MethodPost, ArrWebhookPath, strings.NewReader(`{"eventType":"Download","movie":{"title":"Dune","year":2021}}`)) + rec := httptest.NewRecorder() + handler.ServeHTTP(rec, req) + + if rec.Code != http.StatusAccepted { + t.Fatalf("expected accepted status, got %d", rec.Code) + } + if sender.roomID != "" { + t.Fatalf("expected no notice to be sent, got room %q", sender.roomID) + } +} diff --git a/shells/default/default.nix b/shells/default/default.nix index ed12b5c..76d15b7 100644 --- a/shells/default/default.nix +++ b/shells/default/default.nix @@ -18,5 +18,6 @@ mkShell { openssl inputs.clan-core.packages.${stdenv.hostPlatform.system}.clan-cli nix-output-monitor + dos2unix ]; } diff --git a/systems/x86_64-linux/ulmo/default.nix b/systems/x86_64-linux/ulmo/default.nix index 18c5751..59077d8 100644 --- a/systems/x86_64-linux/ulmo/default.nix +++ b/systems/x86_64-linux/ulmo/default.nix @@ -140,13 +140,13 @@ }; mydia = { - redirectUris = ["http://localhost:2010/auth/oidc/callback"]; + redirectUris = ["http://localhost:2100/auth/oidc/callback"]; grantTypes = ["authorizationCode"]; responseTypes = ["code"]; }; grafana = { - redirectUris = ["http://localhost:9100/login/generic_oauth"]; + redirectUris = ["http://localhost:9010/login/generic_oauth"]; grantTypes = ["authorizationCode"]; responseTypes = ["code"]; }; @@ -224,7 +224,7 @@ media.servarr = { radarr = { enable = true; - port = 2001; + port = 2010; rootFolders = [ "/var/media/movies" ]; @@ -233,7 +233,7 @@ sonarr = { enable = true; # debug = true; - port = 2002; + port = 2020; rootFolders = [ "/var/media/series" ]; @@ -242,7 +242,7 @@ lidarr = { enable = true; debug = true; - port = 2003; + port = 2030; rootFolders = [ "/var/media/music" ]; @@ -251,7 +251,7 @@ prowlarr = { enable = true; # debug = true; - port = 2004; + port = 2040; }; }; diff --git a/vars/per-machine/ulmo/lidarr/api_key/machines/ulmo b/vars/per-machine/ulmo/lidarr/api_key/machines/ulmo new file mode 120000 index 0000000..e5129f9 --- /dev/null +++ b/vars/per-machine/ulmo/lidarr/api_key/machines/ulmo @@ -0,0 +1 @@ +../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/lidarr/api_key/users/chris b/vars/per-machine/ulmo/lidarr/api_key/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/lidarr/api_key/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/lidarr/config.env/machines/ulmo b/vars/per-machine/ulmo/lidarr/config.env/machines/ulmo new file mode 120000 index 0000000..e5129f9 --- /dev/null +++ b/vars/per-machine/ulmo/lidarr/config.env/machines/ulmo @@ -0,0 +1 @@ +../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/lidarr/config.env/users/chris b/vars/per-machine/ulmo/lidarr/config.env/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/lidarr/config.env/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/.pgpass/machines/ulmo b/vars/per-machine/ulmo/postgresql/.pgpass/machines/ulmo new file mode 120000 index 0000000..e5129f9 --- /dev/null +++ b/vars/per-machine/ulmo/postgresql/.pgpass/machines/ulmo @@ -0,0 +1 @@ +../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/.pgpass/users/chris b/vars/per-machine/ulmo/postgresql/.pgpass/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/postgresql/.pgpass/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/lidarr_hash/users/chris b/vars/per-machine/ulmo/postgresql/lidarr_hash/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/postgresql/lidarr_hash/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/lidarr_password/users/chris b/vars/per-machine/ulmo/postgresql/lidarr_password/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/postgresql/lidarr_password/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/prowlarr_hash/users/chris b/vars/per-machine/ulmo/postgresql/prowlarr_hash/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/postgresql/prowlarr_hash/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/prowlarr_password/users/chris b/vars/per-machine/ulmo/postgresql/prowlarr_password/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/postgresql/prowlarr_password/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/radarr_hash/users/chris b/vars/per-machine/ulmo/postgresql/radarr_hash/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/postgresql/radarr_hash/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/radarr_password/users/chris b/vars/per-machine/ulmo/postgresql/radarr_password/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/postgresql/radarr_password/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/server.crt/machines/ulmo b/vars/per-machine/ulmo/postgresql/server.crt/machines/ulmo new file mode 120000 index 0000000..e5129f9 --- /dev/null +++ b/vars/per-machine/ulmo/postgresql/server.crt/machines/ulmo @@ -0,0 +1 @@ +../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/server.crt/users/chris b/vars/per-machine/ulmo/postgresql/server.crt/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/postgresql/server.crt/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/server.key/machines/ulmo b/vars/per-machine/ulmo/postgresql/server.key/machines/ulmo new file mode 120000 index 0000000..e5129f9 --- /dev/null +++ b/vars/per-machine/ulmo/postgresql/server.key/machines/ulmo @@ -0,0 +1 @@ +../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/server.key/users/chris b/vars/per-machine/ulmo/postgresql/server.key/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/postgresql/server.key/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/sonarr_hash/users/chris b/vars/per-machine/ulmo/postgresql/sonarr_hash/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/postgresql/sonarr_hash/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/sonarr_password/users/chris b/vars/per-machine/ulmo/postgresql/sonarr_password/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/postgresql/sonarr_password/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/prowlarr/api_key/machines/ulmo b/vars/per-machine/ulmo/prowlarr/api_key/machines/ulmo new file mode 120000 index 0000000..e5129f9 --- /dev/null +++ b/vars/per-machine/ulmo/prowlarr/api_key/machines/ulmo @@ -0,0 +1 @@ +../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/prowlarr/api_key/users/chris b/vars/per-machine/ulmo/prowlarr/api_key/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/prowlarr/api_key/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/prowlarr/config.env/machines/ulmo b/vars/per-machine/ulmo/prowlarr/config.env/machines/ulmo new file mode 120000 index 0000000..e5129f9 --- /dev/null +++ b/vars/per-machine/ulmo/prowlarr/config.env/machines/ulmo @@ -0,0 +1 @@ +../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/prowlarr/config.env/users/chris b/vars/per-machine/ulmo/prowlarr/config.env/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/prowlarr/config.env/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/qbittorrent/password/machines/ulmo b/vars/per-machine/ulmo/qbittorrent/password/machines/ulmo new file mode 120000 index 0000000..e5129f9 --- /dev/null +++ b/vars/per-machine/ulmo/qbittorrent/password/machines/ulmo @@ -0,0 +1 @@ +../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/qbittorrent/password/users/chris b/vars/per-machine/ulmo/qbittorrent/password/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/qbittorrent/password/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/qbittorrent/password_hash/machines/ulmo b/vars/per-machine/ulmo/qbittorrent/password_hash/machines/ulmo new file mode 120000 index 0000000..e5129f9 --- /dev/null +++ b/vars/per-machine/ulmo/qbittorrent/password_hash/machines/ulmo @@ -0,0 +1 @@ +../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/qbittorrent/password_hash/users/chris b/vars/per-machine/ulmo/qbittorrent/password_hash/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/qbittorrent/password_hash/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/qbittorrent/qBittorrent.conf/machines/ulmo b/vars/per-machine/ulmo/qbittorrent/qBittorrent.conf/machines/ulmo new file mode 120000 index 0000000..e5129f9 --- /dev/null +++ b/vars/per-machine/ulmo/qbittorrent/qBittorrent.conf/machines/ulmo @@ -0,0 +1 @@ +../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/qbittorrent/qBittorrent.conf/users/chris b/vars/per-machine/ulmo/qbittorrent/qBittorrent.conf/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/qbittorrent/qBittorrent.conf/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/radarr/api_key/machines/ulmo b/vars/per-machine/ulmo/radarr/api_key/machines/ulmo new file mode 120000 index 0000000..e5129f9 --- /dev/null +++ b/vars/per-machine/ulmo/radarr/api_key/machines/ulmo @@ -0,0 +1 @@ +../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/radarr/api_key/users/chris b/vars/per-machine/ulmo/radarr/api_key/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/radarr/api_key/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/radarr/config.env/machines/ulmo b/vars/per-machine/ulmo/radarr/config.env/machines/ulmo new file mode 120000 index 0000000..e5129f9 --- /dev/null +++ b/vars/per-machine/ulmo/radarr/config.env/machines/ulmo @@ -0,0 +1 @@ +../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/radarr/config.env/users/chris b/vars/per-machine/ulmo/radarr/config.env/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/radarr/config.env/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/sabnzbd/api_key/machines/ulmo b/vars/per-machine/ulmo/sabnzbd/api_key/machines/ulmo new file mode 120000 index 0000000..e5129f9 --- /dev/null +++ b/vars/per-machine/ulmo/sabnzbd/api_key/machines/ulmo @@ -0,0 +1 @@ +../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/sabnzbd/api_key/users/chris b/vars/per-machine/ulmo/sabnzbd/api_key/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/sabnzbd/api_key/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/sabnzbd/config.ini/machines/ulmo b/vars/per-machine/ulmo/sabnzbd/config.ini/machines/ulmo new file mode 120000 index 0000000..e5129f9 --- /dev/null +++ b/vars/per-machine/ulmo/sabnzbd/config.ini/machines/ulmo @@ -0,0 +1 @@ +../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/sabnzbd/config.ini/users/chris b/vars/per-machine/ulmo/sabnzbd/config.ini/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/sabnzbd/config.ini/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/sabnzbd/nzb_key/machines/ulmo b/vars/per-machine/ulmo/sabnzbd/nzb_key/machines/ulmo new file mode 120000 index 0000000..e5129f9 --- /dev/null +++ b/vars/per-machine/ulmo/sabnzbd/nzb_key/machines/ulmo @@ -0,0 +1 @@ +../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/sabnzbd/nzb_key/users/chris b/vars/per-machine/ulmo/sabnzbd/nzb_key/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/sabnzbd/nzb_key/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/sabnzbd/password/machines/ulmo b/vars/per-machine/ulmo/sabnzbd/password/machines/ulmo new file mode 120000 index 0000000..e5129f9 --- /dev/null +++ b/vars/per-machine/ulmo/sabnzbd/password/machines/ulmo @@ -0,0 +1 @@ +../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/sabnzbd/password/users/chris b/vars/per-machine/ulmo/sabnzbd/password/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/sabnzbd/password/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/sabnzbd/username/machines/ulmo b/vars/per-machine/ulmo/sabnzbd/username/machines/ulmo new file mode 120000 index 0000000..e5129f9 --- /dev/null +++ b/vars/per-machine/ulmo/sabnzbd/username/machines/ulmo @@ -0,0 +1 @@ +../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/sabnzbd/username/users/chris b/vars/per-machine/ulmo/sabnzbd/username/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/sabnzbd/username/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/servarr/config.tfvars/machines/ulmo b/vars/per-machine/ulmo/servarr/config.tfvars/machines/ulmo new file mode 120000 index 0000000..e5129f9 --- /dev/null +++ b/vars/per-machine/ulmo/servarr/config.tfvars/machines/ulmo @@ -0,0 +1 @@ +../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/servarr/config.tfvars/users/chris b/vars/per-machine/ulmo/servarr/config.tfvars/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/servarr/config.tfvars/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/sonarr/api_key/machines/ulmo b/vars/per-machine/ulmo/sonarr/api_key/machines/ulmo new file mode 120000 index 0000000..e5129f9 --- /dev/null +++ b/vars/per-machine/ulmo/sonarr/api_key/machines/ulmo @@ -0,0 +1 @@ +../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/sonarr/api_key/users/chris b/vars/per-machine/ulmo/sonarr/api_key/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/sonarr/api_key/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/sonarr/config.env/machines/ulmo b/vars/per-machine/ulmo/sonarr/config.env/machines/ulmo new file mode 120000 index 0000000..e5129f9 --- /dev/null +++ b/vars/per-machine/ulmo/sonarr/config.env/machines/ulmo @@ -0,0 +1 @@ +../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/sonarr/config.env/users/chris b/vars/per-machine/ulmo/sonarr/config.env/users/chris new file mode 120000 index 0000000..91b7741 --- /dev/null +++ b/vars/per-machine/ulmo/sonarr/config.env/users/chris @@ -0,0 +1 @@ +../../../../../../sops/users/chris \ No newline at end of file From e07257e13723ec55035fc9f41f9b3f3f16feb986 Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Thu, 16 Apr 2026 15:36:33 +0200 Subject: [PATCH 107/108] checkpoint --- flake.nix | 4 ++ .../authentication/zitadel/default.nix | 11 ++- .../services/communication/matrix/default.nix | 36 ++++++++-- .../nixos/services/media/glance/default.nix | 10 +-- .../nixos/services/media/servarr/default.nix | 42 +++++++---- .../services/networking/caddy/default.nix | 2 + .../services/observability/alloy/default.nix | 23 +++--- .../observability/grafana/default.nix | 72 +++++++++---------- .../services/observability/tempo/default.nix | 21 +++--- .../nixos/temp/services/arrtrix/default.nix | 13 ++++ packages/arrtrix/pkg/runtime/envconfig.go | 50 +++++++++++++ .../arrtrix/pkg/runtime/envconfig_test.go | 57 +++++++++++++++ systems/x86_64-linux/ulmo/default.nix | 25 ------- .../ulmo/lidarr/api_key/machines/ulmo | 1 - .../ulmo/lidarr/api_key/users/chris | 1 - .../ulmo/lidarr/config.env/machines/ulmo | 1 - .../ulmo/lidarr/config.env/users/chris | 1 - .../ulmo/postgresql/.pgpass/machines/ulmo | 1 - .../ulmo/postgresql/.pgpass/users/chris | 1 - .../ulmo/postgresql/lidarr_hash/users/chris | 1 - .../postgresql/lidarr_password/users/chris | 1 - .../ulmo/postgresql/prowlarr_hash/users/chris | 1 - .../postgresql/prowlarr_password/users/chris | 1 - .../ulmo/postgresql/radarr_hash/users/chris | 1 - .../postgresql/radarr_password/users/chris | 1 - .../ulmo/postgresql/server.crt/machines/ulmo | 1 - .../ulmo/postgresql/server.crt/users/chris | 1 - .../ulmo/postgresql/server.key/machines/ulmo | 1 - .../ulmo/postgresql/server.key/users/chris | 1 - .../ulmo/postgresql/sonarr_hash/users/chris | 1 - .../postgresql/sonarr_password/users/chris | 1 - .../ulmo/prowlarr/api_key/machines/ulmo | 1 - .../ulmo/prowlarr/api_key/users/chris | 1 - .../ulmo/prowlarr/config.env/machines/ulmo | 1 - .../ulmo/prowlarr/config.env/users/chris | 1 - .../ulmo/qbittorrent/password/machines/ulmo | 1 - .../ulmo/qbittorrent/password/users/chris | 1 - .../qbittorrent/password_hash/machines/ulmo | 1 - .../qbittorrent/password_hash/users/chris | 1 - .../qBittorrent.conf/machines/ulmo | 1 - .../qbittorrent/qBittorrent.conf/users/chris | 1 - .../ulmo/radarr/api_key/machines/ulmo | 1 - .../ulmo/radarr/api_key/users/chris | 1 - .../ulmo/radarr/config.env/machines/ulmo | 1 - .../ulmo/radarr/config.env/users/chris | 1 - .../ulmo/sabnzbd/api_key/machines/ulmo | 1 - .../ulmo/sabnzbd/api_key/users/chris | 1 - .../ulmo/sabnzbd/config.ini/machines/ulmo | 1 - .../ulmo/sabnzbd/config.ini/users/chris | 1 - .../ulmo/sabnzbd/nzb_key/machines/ulmo | 1 - .../ulmo/sabnzbd/nzb_key/users/chris | 1 - .../ulmo/sabnzbd/password/machines/ulmo | 1 - .../ulmo/sabnzbd/password/users/chris | 1 - .../ulmo/sabnzbd/username/machines/ulmo | 1 - .../ulmo/sabnzbd/username/users/chris | 1 - .../ulmo/servarr/config.tfvars/machines/ulmo | 1 - .../ulmo/servarr/config.tfvars/users/chris | 1 - .../ulmo/sonarr/api_key/machines/ulmo | 1 - .../ulmo/sonarr/api_key/users/chris | 1 - .../ulmo/sonarr/config.env/machines/ulmo | 1 - .../ulmo/sonarr/config.env/users/chris | 1 - 61 files changed, 258 insertions(+), 156 deletions(-) create mode 100644 packages/arrtrix/pkg/runtime/envconfig_test.go delete mode 120000 vars/per-machine/ulmo/lidarr/api_key/machines/ulmo delete mode 120000 vars/per-machine/ulmo/lidarr/api_key/users/chris delete mode 120000 vars/per-machine/ulmo/lidarr/config.env/machines/ulmo delete mode 120000 vars/per-machine/ulmo/lidarr/config.env/users/chris delete mode 120000 vars/per-machine/ulmo/postgresql/.pgpass/machines/ulmo delete mode 120000 vars/per-machine/ulmo/postgresql/.pgpass/users/chris delete mode 120000 vars/per-machine/ulmo/postgresql/lidarr_hash/users/chris delete mode 120000 vars/per-machine/ulmo/postgresql/lidarr_password/users/chris delete mode 120000 vars/per-machine/ulmo/postgresql/prowlarr_hash/users/chris delete mode 120000 vars/per-machine/ulmo/postgresql/prowlarr_password/users/chris delete mode 120000 vars/per-machine/ulmo/postgresql/radarr_hash/users/chris delete mode 120000 vars/per-machine/ulmo/postgresql/radarr_password/users/chris delete mode 120000 vars/per-machine/ulmo/postgresql/server.crt/machines/ulmo delete mode 120000 vars/per-machine/ulmo/postgresql/server.crt/users/chris delete mode 120000 vars/per-machine/ulmo/postgresql/server.key/machines/ulmo delete mode 120000 vars/per-machine/ulmo/postgresql/server.key/users/chris delete mode 120000 vars/per-machine/ulmo/postgresql/sonarr_hash/users/chris delete mode 120000 vars/per-machine/ulmo/postgresql/sonarr_password/users/chris delete mode 120000 vars/per-machine/ulmo/prowlarr/api_key/machines/ulmo delete mode 120000 vars/per-machine/ulmo/prowlarr/api_key/users/chris delete mode 120000 vars/per-machine/ulmo/prowlarr/config.env/machines/ulmo delete mode 120000 vars/per-machine/ulmo/prowlarr/config.env/users/chris delete mode 120000 vars/per-machine/ulmo/qbittorrent/password/machines/ulmo delete mode 120000 vars/per-machine/ulmo/qbittorrent/password/users/chris delete mode 120000 vars/per-machine/ulmo/qbittorrent/password_hash/machines/ulmo delete mode 120000 vars/per-machine/ulmo/qbittorrent/password_hash/users/chris delete mode 120000 vars/per-machine/ulmo/qbittorrent/qBittorrent.conf/machines/ulmo delete mode 120000 vars/per-machine/ulmo/qbittorrent/qBittorrent.conf/users/chris delete mode 120000 vars/per-machine/ulmo/radarr/api_key/machines/ulmo delete mode 120000 vars/per-machine/ulmo/radarr/api_key/users/chris delete mode 120000 vars/per-machine/ulmo/radarr/config.env/machines/ulmo delete mode 120000 vars/per-machine/ulmo/radarr/config.env/users/chris delete mode 120000 vars/per-machine/ulmo/sabnzbd/api_key/machines/ulmo delete mode 120000 vars/per-machine/ulmo/sabnzbd/api_key/users/chris delete mode 120000 vars/per-machine/ulmo/sabnzbd/config.ini/machines/ulmo delete mode 120000 vars/per-machine/ulmo/sabnzbd/config.ini/users/chris delete mode 120000 vars/per-machine/ulmo/sabnzbd/nzb_key/machines/ulmo delete mode 120000 vars/per-machine/ulmo/sabnzbd/nzb_key/users/chris delete mode 120000 vars/per-machine/ulmo/sabnzbd/password/machines/ulmo delete mode 120000 vars/per-machine/ulmo/sabnzbd/password/users/chris delete mode 120000 vars/per-machine/ulmo/sabnzbd/username/machines/ulmo delete mode 120000 vars/per-machine/ulmo/sabnzbd/username/users/chris delete mode 120000 vars/per-machine/ulmo/servarr/config.tfvars/machines/ulmo delete mode 120000 vars/per-machine/ulmo/servarr/config.tfvars/users/chris delete mode 120000 vars/per-machine/ulmo/sonarr/api_key/machines/ulmo delete mode 120000 vars/per-machine/ulmo/sonarr/api_key/users/chris delete mode 120000 vars/per-machine/ulmo/sonarr/config.env/machines/ulmo delete mode 120000 vars/per-machine/ulmo/sonarr/config.env/users/chris diff --git a/flake.nix b/flake.nix index 7ccab59..692afe1 100644 --- a/flake.nix +++ b/flake.nix @@ -1,6 +1,10 @@ { description = "Nixos config flake"; + nixConfig = { + warn-dirty = false; + }; + inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; diff --git a/modules/nixos/services/authentication/zitadel/default.nix b/modules/nixos/services/authentication/zitadel/default.nix index 7674835..6e42eeb 100644 --- a/modules/nixos/services/authentication/zitadel/default.nix +++ b/modules/nixos/services/authentication/zitadel/default.nix @@ -1,9 +1,10 @@ { config, lib, pkgs, namespace, system, inputs, ... }: let - inherit (lib) mkIf mkEnableOption mkOption types toUpper toSentenceCase nameValuePair mapAttrs mapAttrs' concatMapAttrs concatMapStringsSep filterAttrsRecursive listToAttrs imap0 head drop length literalExpression attrNames; + inherit (lib) mkIf mkEnableOption mkOption toString types toUpper toSentenceCase nameValuePair mapAttrs mapAttrs' concatMapAttrs concatMapStringsSep filterAttrsRecursive listToAttrs imap0 head drop length literalExpression attrNames; inherit (lib.${namespace}.strings) toSnakeCase; cfg = config.${namespace}.services.authentication.zitadel; + port = 3010; database = "zitadel"; in @@ -543,12 +544,12 @@ in networking.caddy = { hosts = { "auth.kruining.eu" = '' - reverse_proxy h2c://[::1]:9092 + reverse_proxy h2c://[::1]:${toString port} ''; }; extraConfig = '' (auth) { - forward_auth h2c://[::1]:9092 { + forward_auth h2c://[::1]:${toString port} { uri /api/authz/forward-auth copy_headers Remote-User Remote-Groups Remote-Email Remote-Name } @@ -612,7 +613,7 @@ in masterKeyFile = config.sops.secrets."zitadel/masterKey".path; tlsMode = "external"; settings = { - Port = 9092; + Port = port; ExternalDomain = "auth.kruining.eu"; ExternalPort = 443; @@ -698,8 +699,6 @@ in }; }; - networking.firewall.allowedTCPPorts = [ 80 443 ]; - # Secrets sops = { secrets = { diff --git a/modules/nixos/services/communication/matrix/default.nix b/modules/nixos/services/communication/matrix/default.nix index cd5aff2..4661f2a 100644 --- a/modules/nixos/services/communication/matrix/default.nix +++ b/modules/nixos/services/communication/matrix/default.nix @@ -112,9 +112,29 @@ in { (mkMautrix "mautrix-telegram" 2 {}) (mkMautrix "mautrix-whatsapp" 3 {}) (mkMautrix "arrtrix" 4 { - settings.observability = { - otlp_grpc_endpoint = "http://[::1]:1000"; - service_name = "arrtrix"; + environmentFile = config.sops.templates."arrtrix/secrets".path; + + settings = { + observability = { + otlp_grpc_endpoint = "http://[::1]:9062"; + service_name = "arrtrix"; + }; + + network.content = { + movies = { + url = "http://[::1]:${toString config.services.radarr.settings.server.port}"; + api_key = "$RADARR_APIKEY"; + root_folder_path = "/var/media/movies"; + quality_profile_id = 5; + }; + series = { + url = "http://[::1]:${toString config.services.radarr.settings.server.port}"; + api_key = "$SONARR_APIKEY"; + root_folder_path = "/var/media/series"; + quality_profile_id = 5; + language_profile_id = 1; + }; + }; }; }) { @@ -167,7 +187,7 @@ in { }; sso = { - client_whitelist = ["http://[::1]:9092/" "https://auth.kruining.eu/"]; + client_whitelist = ["http://[::1]:${toString config.services.zitadel.settings.Port}/" "https://auth.kruining.eu/"]; update_profile_information = true; }; @@ -365,6 +385,14 @@ in { ''; restartUnits = ["matrix-synapse.service"]; }; + "arrtrix/secrets" = { + owner = "arrtrix"; + content = '' + RADARR_APIKEY=${config.sops.placeholder."radarr/apikey"} + SONARR_APIKEY=${config.sops.placeholder."sonarr/apikey"} + ''; + restartUnits = ["arrtrix.service"]; + }; }; }; }; diff --git a/modules/nixos/services/media/glance/default.nix b/modules/nixos/services/media/glance/default.nix index b042297..bdd4c87 100644 --- a/modules/nixos/services/media/glance/default.nix +++ b/modules/nixos/services/media/glance/default.nix @@ -13,11 +13,11 @@ in { }; config = mkIf cfg.enable { - ${namespace}.services.networking.caddy.hosts = { - "https://${config.networking.hostName}:443" = '' - reverse_proxy http://[::1]:2000 - ''; - }; + # ${namespace}.services.networking.caddy.hosts = { + # "https://${config.networking.hostName}.arda:443" = '' + # reverse_proxy http://[::1]:2000 + # ''; + # }; services.glance = { enable = true; diff --git a/modules/nixos/services/media/servarr/default.nix b/modules/nixos/services/media/servarr/default.nix index 18932b1..ed9b94a 100644 --- a/modules/nixos/services/media/servarr/default.nix +++ b/modules/nixos/services/media/servarr/default.nix @@ -56,7 +56,8 @@ in { auth.authenticationMethod = "External"; server = { - bindaddress = "0.0.0.0"; + # bindaddress = "0.0.0.0"; + bindaddress = "[::]"; port = port; }; @@ -194,7 +195,7 @@ in { source = "devopsarr/${service}"; version = { - radarr = "2.3.3"; + radarr = "2.3.5"; sonarr = "3.4.0"; prowlarr = "3.1.0"; lidarr = "1.13.0"; @@ -217,10 +218,15 @@ in { { method = 1; # HTTP METHOD 1=POST, 2=PUT name = "Arrtrix"; - url = "http://[::1]${toString config'.services.arrtrix.settings.appservice.port}"; + url = "http://localhost:${toString config'.services.arrtrix.settings.appservice.port}/_arrtrix/webhook"; + + on_grab = true; + on_download = true; + on_rename = true; + on_upgrade = true; } // (lib.optionalAttrs (lib.elem service ["radarr" "whisparr"]) { - onMovieDelete = true; + on_movie_delete = true; }); }; @@ -244,15 +250,25 @@ in { }; "${service}_download_client_sabnzbd" = mkIf (lib.elem service ["radarr" "sonarr" "lidarr" "whisparr"]) { - "main" = { - name = "SABnzbd"; - enable = true; - priority = 1; - host = "localhost"; - api_key = lib.tfRef "var.sabnzbd_api_key"; - url_base = "/"; - port = 2090; - }; + "main" = + { + name = "SABnzbd"; + enable = true; + priority = 1; + host = "localhost"; + api_key = lib.tfRef "var.sabnzbd_api_key"; + url_base = "/"; + port = 2090; + } + // ({ + radarr = {movie_category = "movies";}; + sonarr = {tv_category = "tv";}; + lidarr = {music_category = "audio";}; + whisparr = {movie_category = "movies";}; + readarr = {book_category = "Default";}; + }.${ + service + }); }; } // (lib.optionalAttrs (service == "prowlarr") ( diff --git a/modules/nixos/services/networking/caddy/default.nix b/modules/nixos/services/networking/caddy/default.nix index e18a707..21ab908 100644 --- a/modules/nixos/services/networking/caddy/default.nix +++ b/modules/nixos/services/networking/caddy/default.nix @@ -24,6 +24,8 @@ in { }; config = mkIf hasHosts { + networking.firewall.allowedTCPPorts = [80 443]; + services.caddy = { enable = cfg.enable; diff --git a/modules/nixos/services/observability/alloy/default.nix b/modules/nixos/services/observability/alloy/default.nix index c6f5cac..3b64f2e 100644 --- a/modules/nixos/services/observability/alloy/default.nix +++ b/modules/nixos/services/observability/alloy/default.nix @@ -1,5 +1,9 @@ -{ config, lib, namespace, ... }: -let +{ + config, + lib, + namespace, + ... +}: let inherit (builtins) toString; inherit (lib) mkEnableOption mkIf; @@ -9,8 +13,7 @@ let otlpGrpcPort = 9071; otlpHttpPort = 9072; tempoOtlpGrpcPort = 9062; -in -{ +in { options.${namespace}.services.observability.alloy = { enable = mkEnableOption "enable Grafana Alloy"; }; @@ -21,7 +24,7 @@ in configPath = "/etc/alloy"; extraFlags = [ "--disable-reporting" - "--server.http.listen-addr=0.0.0.0:${toString httpPort}" + "--server.http.listen-addr=[::]:${toString httpPort}" "--storage.path=/var/lib/alloy" ]; }; @@ -29,11 +32,11 @@ in environment.etc."alloy/config.alloy".text = '' otelcol.receiver.otlp "default" { grpc { - endpoint = "127.0.0.1:${toString otlpGrpcPort}" + endpoint = "[::1]:${toString otlpGrpcPort}" } http { - endpoint = "127.0.0.1:${toString otlpHttpPort}" + endpoint = "[::1]:${toString otlpHttpPort}" } output { @@ -60,13 +63,13 @@ in prometheus.remote_write "local" { endpoint { - url = "http://127.0.0.1:${toString config.services.prometheus.port}/api/v1/write" + url = "http://[::1]:${toString config.services.prometheus.port}/api/v1/write" } } otelcol.exporter.otlp "tempo" { client { - endpoint = "127.0.0.1:${toString tempoOtlpGrpcPort}" + endpoint = "[::1]:${toString tempoOtlpGrpcPort}" tls { insecure = true @@ -75,6 +78,6 @@ in } ''; - networking.firewall.allowedTCPPorts = [ httpPort ]; + networking.firewall.allowedTCPPorts = [httpPort]; }; } diff --git a/modules/nixos/services/observability/grafana/default.nix b/modules/nixos/services/observability/grafana/default.nix index d9308c0..363033c 100644 --- a/modules/nixos/services/observability/grafana/default.nix +++ b/modules/nixos/services/observability/grafana/default.nix @@ -26,7 +26,7 @@ in { settings = { server = { http_port = 9010; - http_addr = "0.0.0.0"; + http_addr = "::"; domain = "ulmo"; }; @@ -102,43 +102,43 @@ in { }; datasources.settings.datasources = [ - { - name = "Prometheus"; - uid = "prometheus"; - type = "prometheus"; - url = "http://localhost:9020"; - isDefault = true; - editable = false; - } + # { + # name = "Prometheus"; + # uid = "prometheus"; + # type = "prometheus"; + # url = "http://[::1]:9020"; + # isDefault = true; + # editable = false; + # } - { - name = "Loki"; - uid = "loki"; - type = "loki"; - url = "http://localhost:9030"; - editable = false; - } + # { + # name = "Loki"; + # uid = "loki"; + # type = "loki"; + # url = "http://[::1]:9030"; + # editable = false; + # } - { - name = "Tempo"; - uid = "tempo"; - type = "tempo"; - url = "http://localhost:9060"; - editable = false; - jsonData = { - nodeGraph.enabled = true; - serviceMap.datasourceUid = "prometheus"; - tracesToLogsV2 = { - datasourceUid = "loki"; - filterByTraceID = true; - spanStartTimeShift = "-1h"; - spanEndTimeShift = "1h"; - }; - }; - } - ]; - }; - }; + # { + # name = "Tempo"; + # uid = "tempo"; + # type = "tempo"; + # url = "http://localhost:9060"; + # editable = false; + # jsonData = { + # nodeGraph.enabled = true; + # serviceMap.datasourceUid = "prometheus"; + # tracesToLogsV2 = { + # datasourceUid = "loki"; + # filterByTraceID = true; + # spanStartTimeShift = "-1h"; + # spanEndTimeShift = "1h"; + # }; + # }; + # } + ]; + }; + }; postgresql = { enable = true; diff --git a/modules/nixos/services/observability/tempo/default.nix b/modules/nixos/services/observability/tempo/default.nix index dd0602c..46339bc 100644 --- a/modules/nixos/services/observability/tempo/default.nix +++ b/modules/nixos/services/observability/tempo/default.nix @@ -1,5 +1,9 @@ -{ config, lib, namespace, ... }: -let +{ + config, + lib, + namespace, + ... +}: let inherit (lib) mkEnableOption mkIf; cfg = config.${namespace}.services.observability.tempo; @@ -8,8 +12,7 @@ let grpcPort = 9061; otlpGrpcPort = 9062; otlpHttpPort = 9063; -in -{ +in { options.${namespace}.services.observability.tempo = { enable = mkEnableOption "enable Grafana Tempo"; }; @@ -22,15 +25,15 @@ in search_enabled = true; server = { - http_listen_address = "0.0.0.0"; + http_listen_address = "[::]"; http_listen_port = httpPort; - grpc_listen_address = "127.0.0.1"; + grpc_listen_address = "[::1]"; grpc_listen_port = grpcPort; }; distributor.receivers.otlp.protocols = { - grpc.endpoint = "127.0.0.1:${builtins.toString otlpGrpcPort}"; - http.endpoint = "127.0.0.1:${builtins.toString otlpHttpPort}"; + grpc.endpoint = "[::1]:${builtins.toString otlpGrpcPort}"; + http.endpoint = "[::1]:${builtins.toString otlpHttpPort}"; }; storage.trace = { @@ -43,6 +46,6 @@ in }; }; - networking.firewall.allowedTCPPorts = [ httpPort ]; + networking.firewall.allowedTCPPorts = [httpPort]; }; } diff --git a/modules/nixos/temp/services/arrtrix/default.nix b/modules/nixos/temp/services/arrtrix/default.nix index 0e0d5c8..6bb1d9f 100644 --- a/modules/nixos/temp/services/arrtrix/default.nix +++ b/modules/nixos/temp/services/arrtrix/default.nix @@ -106,6 +106,18 @@ in { example = {}; }; + environmentFile = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + File containing environment variables to be passed to the arrtrix service. + If an environment variable `ARRTRIX_BRIDGE_LOGIN_SHARED_SECRET` is set, + then its value will be used in the configuration file for the option + `double_puppet.secrets` without leaking it to the store, using the configured + `homeserver.domain` as key. + ''; + }; + serviceDependencies = lib.mkOption { type = with lib.types; listOf str; default = @@ -168,6 +180,7 @@ in { StateDirectory = baseNameOf dataDir; WorkingDirectory = dataDir; + EnvironmentFile = cfg.environmentFile; ExecStart = '' ${lib.getExe cfg.package} --config='${settingsFile}' --registration='${registrationFile}' diff --git a/packages/arrtrix/pkg/runtime/envconfig.go b/packages/arrtrix/pkg/runtime/envconfig.go index f8ffd13..173de78 100644 --- a/packages/arrtrix/pkg/runtime/envconfig.go +++ b/packages/arrtrix/pkg/runtime/envconfig.go @@ -36,12 +36,16 @@ func updateConfigFromEnv(cfg, networkData any, prefix string) error { } key = strings.ToLower(key) + lookupKey := key if !strings.ContainsRune(key, '.') { key = strings.ReplaceAll(key, "__", ".") } path := strings.Split(key, ".") field, ok := reflectGetFromMainOrNetwork(cfgVal, networkVal, path) + if !ok && !strings.ContainsRune(lookupKey, '.') { + field, ok = reflectGetFromMainOrNetworkTokens(cfgVal, networkVal, strings.Split(lookupKey, "_")) + } if !ok { return fmt.Errorf("%s not found", formatKey(path)) } @@ -80,6 +84,13 @@ func reflectGetFromMainOrNetwork(main, network reflect.Value, path []string) (*r return reflectGetYAML(main, path) } +func reflectGetFromMainOrNetworkTokens(main, network reflect.Value, tokens []string) (*reflectedField, bool) { + if len(tokens) > 0 && normalizeKey(tokens[0]) == "network" { + return reflectGetYAMLTokens(network, tokens[1:]) + } + return reflectGetYAMLTokens(main, tokens) +} + func reflectGetYAML(value reflect.Value, path []string) (*reflectedField, bool) { if len(path) == 0 { return &reflectedField{value: value, valueKind: value.Kind()}, true @@ -108,6 +119,41 @@ func reflectGetYAML(value reflect.Value, path []string) (*reflectedField, bool) return nil, false } +func reflectGetYAMLTokens(value reflect.Value, tokens []string) (*reflectedField, bool) { + if len(tokens) == 0 { + return &reflectedField{value: value, valueKind: value.Kind()}, true + } + if value.Kind() == reflect.Ptr { + value = value.Elem() + } + + switch value.Kind() { + case reflect.Map: + return &reflectedField{ + value: value, + valueKind: value.Type().Elem().Kind(), + remainingPath: []string{strings.Join(tokens, "_")}, + }, true + case reflect.Struct: + fields := reflect.VisibleFields(value.Type()) + for _, field := range fields { + name := yamlFieldName(field) + if name == "" { + continue + } + normalizedFieldName := normalizeKey(name) + for i := len(tokens); i >= 1; i-- { + if normalizeKey(strings.Join(tokens[:i], "_")) != normalizedFieldName { + continue + } + return reflectGetYAMLTokens(value.FieldByIndex(field.Index), tokens[i:]) + } + } + } + + return nil, false +} + func yamlFieldName(field reflect.StructField) string { parts := strings.SplitN(field.Tag.Get("yaml"), ",", 2) switch name := parts[0]; { @@ -120,6 +166,10 @@ func yamlFieldName(field reflect.StructField) string { } } +func normalizeKey(value string) string { + return strings.ReplaceAll(strings.ToLower(value), "_", "") +} + func setReflectedValue(field *reflectedField, path []string, raw string) error { parsed, err := parseValue(field.valueKind, raw, path) if err != nil { diff --git a/packages/arrtrix/pkg/runtime/envconfig_test.go b/packages/arrtrix/pkg/runtime/envconfig_test.go new file mode 100644 index 0000000..6381a47 --- /dev/null +++ b/packages/arrtrix/pkg/runtime/envconfig_test.go @@ -0,0 +1,57 @@ +package runtime + +import ( + "os" + "testing" + + "maunium.net/go/mautrix/bridgev2/bridgeconfig" + + "sneeuwvlok/packages/arrtrix/pkg/connector" +) + +func TestUpdateConfigFromEnvSupportsFlatUnderscorePaths(t *testing.T) { + t.Setenv("ARRTRIX_NETWORK_CONTENT_MOVIES_APIKEY", "radarr-secret") + + cfg := &bridgeconfig.Config{} + network := &connector.Config{} + if err := updateConfigFromEnv(cfg, network, "ARRTRIX_"); err != nil { + t.Fatalf("updateConfigFromEnv returned error: %v", err) + } + + if network.Content.Movies.APIKey != "radarr-secret" { + t.Fatalf("expected movies api key to be overridden, got %q", network.Content.Movies.APIKey) + } +} + +func TestUpdateConfigFromEnvSupportsExplicitUnderscoredFieldNames(t *testing.T) { + t.Setenv("ARRTRIX_NETWORK_CONTENT_MOVIES_ROOT_FOLDER_PATH", "/data/movies") + + cfg := &bridgeconfig.Config{} + network := &connector.Config{} + if err := updateConfigFromEnv(cfg, network, "ARRTRIX_"); err != nil { + t.Fatalf("updateConfigFromEnv returned error: %v", err) + } + + if network.Content.Movies.RootFolderPath != "/data/movies" { + t.Fatalf("expected root folder path to be overridden, got %q", network.Content.Movies.RootFolderPath) + } +} + +func TestUpdateConfigFromEnvSupportsDoubleUnderscorePaths(t *testing.T) { + t.Setenv("ARRTRIX_NETWORK__CONTENT__SERIES__API_KEY", "sonarr-secret") + + cfg := &bridgeconfig.Config{} + network := &connector.Config{} + if err := updateConfigFromEnv(cfg, network, "ARRTRIX_"); err != nil { + t.Fatalf("updateConfigFromEnv returned error: %v", err) + } + + if network.Content.Series.APIKey != "sonarr-secret" { + t.Fatalf("expected series api key to be overridden, got %q", network.Content.Series.APIKey) + } +} + +func TestMain(m *testing.M) { + code := m.Run() + os.Exit(code) +} diff --git a/systems/x86_64-linux/ulmo/default.nix b/systems/x86_64-linux/ulmo/default.nix index 59077d8..fd25824 100644 --- a/systems/x86_64-linux/ulmo/default.nix +++ b/systems/x86_64-linux/ulmo/default.nix @@ -39,31 +39,6 @@ }; }; - # virtualisation = { - # containers.enable = true; - # podman = { - # enable = true; - # dockerCompat = true; - # }; - - # oci-containers = { - # backend = "podman"; - # containers = { - # homey = { - # image = "ghcr.io/athombv/homey-shs:latest"; - # autoStart = true; - # privileged = true; - # volumes = [ - # "/home/chris/.homey-shs:/homey/user" - # ]; - # ports = [ - # "4859:4859" - # ]; - # }; - # }; - # }; - # }; - sneeuwvlok = { services = { backup.borg.enable = true; diff --git a/vars/per-machine/ulmo/lidarr/api_key/machines/ulmo b/vars/per-machine/ulmo/lidarr/api_key/machines/ulmo deleted file mode 120000 index e5129f9..0000000 --- a/vars/per-machine/ulmo/lidarr/api_key/machines/ulmo +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/lidarr/api_key/users/chris b/vars/per-machine/ulmo/lidarr/api_key/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/lidarr/api_key/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/lidarr/config.env/machines/ulmo b/vars/per-machine/ulmo/lidarr/config.env/machines/ulmo deleted file mode 120000 index e5129f9..0000000 --- a/vars/per-machine/ulmo/lidarr/config.env/machines/ulmo +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/lidarr/config.env/users/chris b/vars/per-machine/ulmo/lidarr/config.env/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/lidarr/config.env/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/.pgpass/machines/ulmo b/vars/per-machine/ulmo/postgresql/.pgpass/machines/ulmo deleted file mode 120000 index e5129f9..0000000 --- a/vars/per-machine/ulmo/postgresql/.pgpass/machines/ulmo +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/.pgpass/users/chris b/vars/per-machine/ulmo/postgresql/.pgpass/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/postgresql/.pgpass/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/lidarr_hash/users/chris b/vars/per-machine/ulmo/postgresql/lidarr_hash/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/postgresql/lidarr_hash/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/lidarr_password/users/chris b/vars/per-machine/ulmo/postgresql/lidarr_password/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/postgresql/lidarr_password/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/prowlarr_hash/users/chris b/vars/per-machine/ulmo/postgresql/prowlarr_hash/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/postgresql/prowlarr_hash/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/prowlarr_password/users/chris b/vars/per-machine/ulmo/postgresql/prowlarr_password/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/postgresql/prowlarr_password/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/radarr_hash/users/chris b/vars/per-machine/ulmo/postgresql/radarr_hash/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/postgresql/radarr_hash/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/radarr_password/users/chris b/vars/per-machine/ulmo/postgresql/radarr_password/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/postgresql/radarr_password/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/server.crt/machines/ulmo b/vars/per-machine/ulmo/postgresql/server.crt/machines/ulmo deleted file mode 120000 index e5129f9..0000000 --- a/vars/per-machine/ulmo/postgresql/server.crt/machines/ulmo +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/server.crt/users/chris b/vars/per-machine/ulmo/postgresql/server.crt/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/postgresql/server.crt/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/server.key/machines/ulmo b/vars/per-machine/ulmo/postgresql/server.key/machines/ulmo deleted file mode 120000 index e5129f9..0000000 --- a/vars/per-machine/ulmo/postgresql/server.key/machines/ulmo +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/server.key/users/chris b/vars/per-machine/ulmo/postgresql/server.key/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/postgresql/server.key/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/sonarr_hash/users/chris b/vars/per-machine/ulmo/postgresql/sonarr_hash/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/postgresql/sonarr_hash/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/postgresql/sonarr_password/users/chris b/vars/per-machine/ulmo/postgresql/sonarr_password/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/postgresql/sonarr_password/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/prowlarr/api_key/machines/ulmo b/vars/per-machine/ulmo/prowlarr/api_key/machines/ulmo deleted file mode 120000 index e5129f9..0000000 --- a/vars/per-machine/ulmo/prowlarr/api_key/machines/ulmo +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/prowlarr/api_key/users/chris b/vars/per-machine/ulmo/prowlarr/api_key/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/prowlarr/api_key/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/prowlarr/config.env/machines/ulmo b/vars/per-machine/ulmo/prowlarr/config.env/machines/ulmo deleted file mode 120000 index e5129f9..0000000 --- a/vars/per-machine/ulmo/prowlarr/config.env/machines/ulmo +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/prowlarr/config.env/users/chris b/vars/per-machine/ulmo/prowlarr/config.env/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/prowlarr/config.env/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/qbittorrent/password/machines/ulmo b/vars/per-machine/ulmo/qbittorrent/password/machines/ulmo deleted file mode 120000 index e5129f9..0000000 --- a/vars/per-machine/ulmo/qbittorrent/password/machines/ulmo +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/qbittorrent/password/users/chris b/vars/per-machine/ulmo/qbittorrent/password/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/qbittorrent/password/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/qbittorrent/password_hash/machines/ulmo b/vars/per-machine/ulmo/qbittorrent/password_hash/machines/ulmo deleted file mode 120000 index e5129f9..0000000 --- a/vars/per-machine/ulmo/qbittorrent/password_hash/machines/ulmo +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/qbittorrent/password_hash/users/chris b/vars/per-machine/ulmo/qbittorrent/password_hash/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/qbittorrent/password_hash/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/qbittorrent/qBittorrent.conf/machines/ulmo b/vars/per-machine/ulmo/qbittorrent/qBittorrent.conf/machines/ulmo deleted file mode 120000 index e5129f9..0000000 --- a/vars/per-machine/ulmo/qbittorrent/qBittorrent.conf/machines/ulmo +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/qbittorrent/qBittorrent.conf/users/chris b/vars/per-machine/ulmo/qbittorrent/qBittorrent.conf/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/qbittorrent/qBittorrent.conf/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/radarr/api_key/machines/ulmo b/vars/per-machine/ulmo/radarr/api_key/machines/ulmo deleted file mode 120000 index e5129f9..0000000 --- a/vars/per-machine/ulmo/radarr/api_key/machines/ulmo +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/radarr/api_key/users/chris b/vars/per-machine/ulmo/radarr/api_key/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/radarr/api_key/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/radarr/config.env/machines/ulmo b/vars/per-machine/ulmo/radarr/config.env/machines/ulmo deleted file mode 120000 index e5129f9..0000000 --- a/vars/per-machine/ulmo/radarr/config.env/machines/ulmo +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/radarr/config.env/users/chris b/vars/per-machine/ulmo/radarr/config.env/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/radarr/config.env/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/sabnzbd/api_key/machines/ulmo b/vars/per-machine/ulmo/sabnzbd/api_key/machines/ulmo deleted file mode 120000 index e5129f9..0000000 --- a/vars/per-machine/ulmo/sabnzbd/api_key/machines/ulmo +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/sabnzbd/api_key/users/chris b/vars/per-machine/ulmo/sabnzbd/api_key/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/sabnzbd/api_key/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/sabnzbd/config.ini/machines/ulmo b/vars/per-machine/ulmo/sabnzbd/config.ini/machines/ulmo deleted file mode 120000 index e5129f9..0000000 --- a/vars/per-machine/ulmo/sabnzbd/config.ini/machines/ulmo +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/sabnzbd/config.ini/users/chris b/vars/per-machine/ulmo/sabnzbd/config.ini/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/sabnzbd/config.ini/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/sabnzbd/nzb_key/machines/ulmo b/vars/per-machine/ulmo/sabnzbd/nzb_key/machines/ulmo deleted file mode 120000 index e5129f9..0000000 --- a/vars/per-machine/ulmo/sabnzbd/nzb_key/machines/ulmo +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/sabnzbd/nzb_key/users/chris b/vars/per-machine/ulmo/sabnzbd/nzb_key/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/sabnzbd/nzb_key/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/sabnzbd/password/machines/ulmo b/vars/per-machine/ulmo/sabnzbd/password/machines/ulmo deleted file mode 120000 index e5129f9..0000000 --- a/vars/per-machine/ulmo/sabnzbd/password/machines/ulmo +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/sabnzbd/password/users/chris b/vars/per-machine/ulmo/sabnzbd/password/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/sabnzbd/password/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/sabnzbd/username/machines/ulmo b/vars/per-machine/ulmo/sabnzbd/username/machines/ulmo deleted file mode 120000 index e5129f9..0000000 --- a/vars/per-machine/ulmo/sabnzbd/username/machines/ulmo +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/sabnzbd/username/users/chris b/vars/per-machine/ulmo/sabnzbd/username/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/sabnzbd/username/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/servarr/config.tfvars/machines/ulmo b/vars/per-machine/ulmo/servarr/config.tfvars/machines/ulmo deleted file mode 120000 index e5129f9..0000000 --- a/vars/per-machine/ulmo/servarr/config.tfvars/machines/ulmo +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/servarr/config.tfvars/users/chris b/vars/per-machine/ulmo/servarr/config.tfvars/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/servarr/config.tfvars/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/sonarr/api_key/machines/ulmo b/vars/per-machine/ulmo/sonarr/api_key/machines/ulmo deleted file mode 120000 index e5129f9..0000000 --- a/vars/per-machine/ulmo/sonarr/api_key/machines/ulmo +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/sonarr/api_key/users/chris b/vars/per-machine/ulmo/sonarr/api_key/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/sonarr/api_key/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file diff --git a/vars/per-machine/ulmo/sonarr/config.env/machines/ulmo b/vars/per-machine/ulmo/sonarr/config.env/machines/ulmo deleted file mode 120000 index e5129f9..0000000 --- a/vars/per-machine/ulmo/sonarr/config.env/machines/ulmo +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/machines/ulmo \ No newline at end of file diff --git a/vars/per-machine/ulmo/sonarr/config.env/users/chris b/vars/per-machine/ulmo/sonarr/config.env/users/chris deleted file mode 120000 index 91b7741..0000000 --- a/vars/per-machine/ulmo/sonarr/config.env/users/chris +++ /dev/null @@ -1 +0,0 @@ -../../../../../../sops/users/chris \ No newline at end of file From 100a218aed5ba11ce4ff77b5c6d162bfe221955c Mon Sep 17 00:00:00 2001 From: Chris Kruining Date: Thu, 16 Apr 2026 16:55:52 +0200 Subject: [PATCH 108/108] Add poster image support to Matrix download listings - Fetch and display poster images for tracked items in Matrix - Show monitored/unmonitored icons in listings - Limit displayed items to 12, with count and overflow message - Add tests for image fetching and formatting - Enable Grafana datasources - Fix Sonarr/Radarr URL config bug --- .../services/communication/matrix/default.nix | 4 +- .../observability/grafana/default.nix | 64 ++++---- packages/arrtrix/pkg/arrclient/client.go | 152 ++++++++++++++++++ packages/arrtrix/pkg/arrclient/client_test.go | 80 +++++++++ packages/arrtrix/pkg/arrclient/radarr.go | 22 ++- packages/arrtrix/pkg/arrclient/sonarr.go | 22 ++- packages/arrtrix/pkg/matrixcmd/download.go | 58 +++++-- .../arrtrix/pkg/matrixcmd/download_test.go | 44 +++++ packages/arrtrix/pkg/matrixcmd/processor.go | 45 +++++- 9 files changed, 432 insertions(+), 59 deletions(-) create mode 100644 packages/arrtrix/pkg/arrclient/client_test.go create mode 100644 packages/arrtrix/pkg/matrixcmd/download_test.go diff --git a/modules/nixos/services/communication/matrix/default.nix b/modules/nixos/services/communication/matrix/default.nix index 4661f2a..9a7d53c 100644 --- a/modules/nixos/services/communication/matrix/default.nix +++ b/modules/nixos/services/communication/matrix/default.nix @@ -116,7 +116,7 @@ in { settings = { observability = { - otlp_grpc_endpoint = "http://[::1]:9062"; + otlp_grpc_endpoint = "http://[::1]:9071"; service_name = "arrtrix"; }; @@ -128,7 +128,7 @@ in { quality_profile_id = 5; }; series = { - url = "http://[::1]:${toString config.services.radarr.settings.server.port}"; + url = "http://[::1]:${toString config.services.sonarr.settings.server.port}"; api_key = "$SONARR_APIKEY"; root_folder_path = "/var/media/series"; quality_profile_id = 5; diff --git a/modules/nixos/services/observability/grafana/default.nix b/modules/nixos/services/observability/grafana/default.nix index 363033c..879ecdc 100644 --- a/modules/nixos/services/observability/grafana/default.nix +++ b/modules/nixos/services/observability/grafana/default.nix @@ -102,40 +102,40 @@ in { }; datasources.settings.datasources = [ - # { - # name = "Prometheus"; - # uid = "prometheus"; - # type = "prometheus"; - # url = "http://[::1]:9020"; - # isDefault = true; - # editable = false; - # } + { + name = "Prometheus"; + uid = "prometheus"; + type = "prometheus"; + url = "http://[::1]:9020"; + isDefault = true; + editable = false; + } - # { - # name = "Loki"; - # uid = "loki"; - # type = "loki"; - # url = "http://[::1]:9030"; - # editable = false; - # } + { + name = "Loki"; + uid = "loki"; + type = "loki"; + url = "http://[::1]:9030"; + editable = false; + } - # { - # name = "Tempo"; - # uid = "tempo"; - # type = "tempo"; - # url = "http://localhost:9060"; - # editable = false; - # jsonData = { - # nodeGraph.enabled = true; - # serviceMap.datasourceUid = "prometheus"; - # tracesToLogsV2 = { - # datasourceUid = "loki"; - # filterByTraceID = true; - # spanStartTimeShift = "-1h"; - # spanEndTimeShift = "1h"; - # }; - # }; - # } + { + name = "Tempo"; + uid = "tempo"; + type = "tempo"; + url = "http://localhost:9060"; + editable = false; + jsonData = { + nodeGraph.enabled = true; + serviceMap.datasourceUid = "prometheus"; + tracesToLogsV2 = { + datasourceUid = "loki"; + filterByTraceID = true; + spanStartTimeShift = "-1h"; + spanEndTimeShift = "1h"; + }; + }; + } ]; }; }; diff --git a/packages/arrtrix/pkg/arrclient/client.go b/packages/arrtrix/pkg/arrclient/client.go index 558dc52..fc7fb53 100644 --- a/packages/arrtrix/pkg/arrclient/client.go +++ b/packages/arrtrix/pkg/arrclient/client.go @@ -5,10 +5,13 @@ import ( "context" "encoding/json" "fmt" + "html" "io" + "mime" "net/http" "net/url" "path" + "path/filepath" "strings" "sneeuwvlok/packages/arrtrix/pkg/arr" @@ -21,6 +24,7 @@ type Client interface { Add(context.Context, SearchResult) (*ManagedItem, error) SetMonitored(context.Context, int64, bool) (*ManagedItem, error) Delete(context.Context, int64) error + FetchImage(context.Context, ManagedItem) (*MediaAsset, error) } type SearchResult struct { @@ -37,6 +41,13 @@ type ManagedItem struct { Year int Monitored bool Path string + ImageURL string +} + +type MediaAsset struct { + Data []byte + FileName string + MimeType string } type RadarrConfig struct { @@ -65,6 +76,12 @@ type httpClient struct { httpClient *http.Client } +type mediaImage struct { + CoverType string `json:"coverType"` + URL string `json:"url"` + RemoteURL string `json:"remoteUrl"` +} + func (c *RadarrConfig) ApplyDefaults() { if c.MinimumAvailability == "" { c.MinimumAvailability = "released" @@ -209,3 +226,138 @@ func FormatSearchResult(result SearchResult) string { } return result.Title } + +func FormatManagedItem(item ManagedItem) string { + if item.Year != 0 { + return fmt.Sprintf("%s (%d)", item.Title, item.Year) + } + return item.Title +} + +func EscapeText(text string) string { + return html.EscapeString(text) +} + +func (c *httpClient) FetchImage(ctx context.Context, item ManagedItem) (*MediaAsset, error) { + imageURL := strings.TrimSpace(item.ImageURL) + if imageURL == "" { + return nil, nil + } + + endpoint, err := url.Parse(imageURL) + if err != nil { + return nil, fmt.Errorf("parse image URL: %w", err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL.ResolveReference(endpoint).String(), nil) + if err != nil { + return nil, err + } + if sameHost(req.URL, c.baseURL) { + req.Header.Set("X-Api-Key", c.apiKey) + } + + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + data, _ := io.ReadAll(io.LimitReader(resp.Body, 4096)) + return nil, fmt.Errorf("GET %s returned %d: %s", req.URL.String(), resp.StatusCode, strings.TrimSpace(string(data))) + } + + data, err := io.ReadAll(io.LimitReader(resp.Body, 10<<20)) + if err != nil { + return nil, err + } + + mimeType := strings.TrimSpace(resp.Header.Get("Content-Type")) + if idx := strings.Index(mimeType, ";"); idx >= 0 { + mimeType = strings.TrimSpace(mimeType[:idx]) + } + if mimeType == "" { + mimeType = http.DetectContentType(data) + } + + return &MediaAsset{ + Data: data, + FileName: imageFileName(item, endpoint, mimeType), + MimeType: mimeType, + }, nil +} + +func (c *httpClient) imageURL(images []mediaImage) string { + for _, coverType := range []string{"poster", "cover", "fanart"} { + for _, image := range images { + if !strings.EqualFold(image.CoverType, coverType) { + continue + } + if resolved := c.resolveMediaURL(image); resolved != "" { + return resolved + } + } + } + return "" +} + +func (c *httpClient) resolveMediaURL(image mediaImage) string { + switch { + case strings.TrimSpace(image.URL) != "": + ref, err := url.Parse(strings.TrimSpace(image.URL)) + if err != nil { + return "" + } + return c.baseURL.ResolveReference(ref).String() + case strings.TrimSpace(image.RemoteURL) != "": + return strings.TrimSpace(image.RemoteURL) + default: + return "" + } +} + +func imageFileName(item ManagedItem, endpoint *url.URL, mimeType string) string { + baseName := sanitizeFileName(strings.TrimSpace(item.Title)) + if baseName == "" { + baseName = fmt.Sprintf("arrtrix-%d", item.ID) + } + + ext := strings.TrimSpace(filepath.Ext(endpoint.Path)) + if ext == "" && mimeType != "" { + if extensions, err := mime.ExtensionsByType(mimeType); err == nil && len(extensions) > 0 { + ext = extensions[0] + } + } + if ext == "" { + ext = ".jpg" + } + if item.ID != 0 { + return fmt.Sprintf("%s-%d%s", baseName, item.ID, ext) + } + return baseName + ext +} + +func sanitizeFileName(value string) string { + replacer := strings.NewReplacer( + "<", "", + ">", "", + ":", "", + "\"", "", + "/", "-", + "\\", "-", + "|", "-", + "?", "", + "*", "", + ) + value = replacer.Replace(value) + value = strings.Join(strings.Fields(value), "-") + return strings.Trim(value, ".- ") +} + +func sameHost(left, right *url.URL) bool { + if left == nil || right == nil { + return false + } + return strings.EqualFold(left.Scheme, right.Scheme) && strings.EqualFold(left.Host, right.Host) +} diff --git a/packages/arrtrix/pkg/arrclient/client_test.go b/packages/arrtrix/pkg/arrclient/client_test.go new file mode 100644 index 0000000..ecce6c3 --- /dev/null +++ b/packages/arrtrix/pkg/arrclient/client_test.go @@ -0,0 +1,80 @@ +package arrclient + +import ( + "context" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" +) + +func TestImageURLPrefersPosterAndResolvesRelativePath(t *testing.T) { + baseURL, err := url.Parse("https://radarr.example") + if err != nil { + t.Fatalf("failed to parse base URL: %v", err) + } + + client := &httpClient{baseURL: baseURL} + imageURL := client.imageURL([]mediaImage{ + {CoverType: "fanart", URL: "/MediaCover/1/fanart.jpg"}, + {CoverType: "poster", URL: "/MediaCover/1/poster.jpg"}, + }) + if imageURL != "https://radarr.example/MediaCover/1/poster.jpg" { + t.Fatalf("unexpected image URL %q", imageURL) + } +} + +func TestImageURLFallsBackToRemoteURL(t *testing.T) { + baseURL, err := url.Parse("https://sonarr.example") + if err != nil { + t.Fatalf("failed to parse base URL: %v", err) + } + + client := &httpClient{baseURL: baseURL} + imageURL := client.imageURL([]mediaImage{ + {CoverType: "poster", RemoteURL: "https://images.example/poster.jpg"}, + }) + if imageURL != "https://images.example/poster.jpg" { + t.Fatalf("unexpected remote image URL %q", imageURL) + } +} + +func TestFetchImageUsesAPIKeyForSameHost(t *testing.T) { + headers := make(chan string, 1) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + headers <- r.Header.Get("X-Api-Key") + w.Header().Set("Content-Type", "image/jpeg") + _, _ = w.Write([]byte("jpeg-bytes")) + })) + defer server.Close() + + client, err := newHTTPClient(server.URL, "secret") + if err != nil { + t.Fatalf("failed to create client: %v", err) + } + + asset, err := client.FetchImage(context.Background(), ManagedItem{ + ID: 42, + Title: "Dune Part Two", + ImageURL: server.URL + "/MediaCover/42/poster.jpg", + }) + if err != nil { + t.Fatalf("failed to fetch image: %v", err) + } + if asset == nil { + t.Fatal("expected media asset") + } + if got := <-headers; got != "secret" { + t.Fatalf("expected API key header, got %q", got) + } + if got := string(asset.Data); got != "jpeg-bytes" { + t.Fatalf("unexpected media bytes %q", got) + } + if asset.MimeType != "image/jpeg" { + t.Fatalf("unexpected mime type %q", asset.MimeType) + } + if !strings.HasPrefix(asset.FileName, "Dune-Part-Two-42") || !strings.HasSuffix(asset.FileName, ".jpg") { + t.Fatalf("unexpected filename %q", asset.FileName) + } +} diff --git a/packages/arrtrix/pkg/arrclient/radarr.go b/packages/arrtrix/pkg/arrclient/radarr.go index 21ac1fd..e214ce3 100644 --- a/packages/arrtrix/pkg/arrclient/radarr.go +++ b/packages/arrtrix/pkg/arrclient/radarr.go @@ -17,13 +17,14 @@ type RadarrClient struct { } type radarrMovie struct { - ID int64 `json:"id"` - Title string `json:"title"` - Year int `json:"year"` - TMDBID int64 `json:"tmdbId"` - Overview string `json:"overview"` - Monitored bool `json:"monitored"` - Path string `json:"path"` + ID int64 `json:"id"` + Title string `json:"title"` + Year int `json:"year"` + TMDBID int64 `json:"tmdbId"` + Overview string `json:"overview"` + Monitored bool `json:"monitored"` + Path string `json:"path"` + Images []mediaImage `json:"images"` } func NewRadarrClient(config RadarrConfig) (*RadarrClient, error) { @@ -81,6 +82,7 @@ func (c *RadarrClient) List(ctx context.Context, query string) ([]ManagedItem, e Year: movie.Year, Monitored: movie.Monitored, Path: movie.Path, + ImageURL: c.http.imageURL(movie.Images), }) } return items, nil @@ -111,6 +113,7 @@ func (c *RadarrClient) Add(ctx context.Context, result SearchResult) (*ManagedIt Year: response.Year, Monitored: response.Monitored, Path: response.Path, + ImageURL: c.http.imageURL(response.Images), } return &item, nil } @@ -134,6 +137,7 @@ func (c *RadarrClient) SetMonitored(ctx context.Context, id int64, monitored boo Year: response.Year, Monitored: response.Monitored, Path: response.Path, + ImageURL: c.http.imageURL(response.Images), } return &item, nil } @@ -145,6 +149,10 @@ func (c *RadarrClient) Delete(ctx context.Context, id int64) error { }, nil, nil) } +func (c *RadarrClient) FetchImage(ctx context.Context, item ManagedItem) (*MediaAsset, error) { + return c.http.FetchImage(ctx, item) +} + func PickSingleResult(results []SearchResult, query string) (SearchResult, error) { switch len(results) { case 0: diff --git a/packages/arrtrix/pkg/arrclient/sonarr.go b/packages/arrtrix/pkg/arrclient/sonarr.go index 9b0691b..caa6cec 100644 --- a/packages/arrtrix/pkg/arrclient/sonarr.go +++ b/packages/arrtrix/pkg/arrclient/sonarr.go @@ -16,13 +16,14 @@ type SonarrClient struct { } type sonarrSeries struct { - ID int64 `json:"id"` - Title string `json:"title"` - Year int `json:"year"` - TVDBID int64 `json:"tvdbId"` - Overview string `json:"overview"` - Monitored bool `json:"monitored"` - Path string `json:"path"` + ID int64 `json:"id"` + Title string `json:"title"` + Year int `json:"year"` + TVDBID int64 `json:"tvdbId"` + Overview string `json:"overview"` + Monitored bool `json:"monitored"` + Path string `json:"path"` + Images []mediaImage `json:"images"` } func NewSonarrClient(config SonarrConfig) (*SonarrClient, error) { @@ -80,6 +81,7 @@ func (c *SonarrClient) List(ctx context.Context, query string) ([]ManagedItem, e Year: series.Year, Monitored: series.Monitored, Path: series.Path, + ImageURL: c.http.imageURL(series.Images), }) } return items, nil @@ -114,6 +116,7 @@ func (c *SonarrClient) Add(ctx context.Context, result SearchResult) (*ManagedIt Year: response.Year, Monitored: response.Monitored, Path: response.Path, + ImageURL: c.http.imageURL(response.Images), } return &item, nil } @@ -137,6 +140,7 @@ func (c *SonarrClient) SetMonitored(ctx context.Context, id int64, monitored boo Year: response.Year, Monitored: response.Monitored, Path: response.Path, + ImageURL: c.http.imageURL(response.Images), } return &item, nil } @@ -147,3 +151,7 @@ func (c *SonarrClient) Delete(ctx context.Context, id int64) error { "addImportListExclusion": {"false"}, }, nil, nil) } + +func (c *SonarrClient) FetchImage(ctx context.Context, item ManagedItem) (*MediaAsset, error) { + return c.http.FetchImage(ctx, item) +} diff --git a/packages/arrtrix/pkg/matrixcmd/download.go b/packages/arrtrix/pkg/matrixcmd/download.go index 6d27a1a..23414b1 100644 --- a/packages/arrtrix/pkg/matrixcmd/download.go +++ b/packages/arrtrix/pkg/matrixcmd/download.go @@ -73,16 +73,22 @@ func handleDownloadList(ctx *Context, client arrclient.Client, contentType arr.C return } - var builder strings.Builder - builder.WriteString(fmt.Sprintf("Tracked %s:\n", contentType.Label())) + count := len(items) + if count > 12 { + count = 12 + } + ctx.Reply("Tracked %s (showing %d of %d):", contentType.Label(), count, len(items)) for i, item := range items { - if i == 10 { - builder.WriteString("…\n") + if i == 12 { break } - builder.WriteString(fmt.Sprintf("- `%d` %s β€” monitored=%t\n", item.ID, formatManagedItem(item), item.Monitored)) + if err := replyWithManagedItem(ctx, client, item); err != nil { + ctx.Log.Err(err).Int64("item_id", item.ID).Str("content_type", contentType.Label()).Msg("Failed to send Matrix-native image for download listing") + } + } + if len(items) > 12 { + ctx.Reply("…and %d more.", len(items)-12) } - ctx.Reply(builder.String()) } func handleDownloadSearch(ctx *Context, client arrclient.Client, contentType arr.ContentType) { @@ -200,10 +206,7 @@ func replyWithSearchResults(ctx *Context, contentType arr.ContentType, query str } func formatManagedItem(item arrclient.ManagedItem) string { - if item.Year != 0 { - return fmt.Sprintf("%s (%d)", item.Title, item.Year) - } - return item.Title + return arrclient.FormatManagedItem(item) } func parseEnabled(value string) (bool, error) { @@ -220,3 +223,38 @@ func parseEnabled(value string) (bool, error) { func userIDString(userID id.UserID) string { return userID.String() } + +func replyWithManagedItem(ctx *Context, client arrclient.Client, item arrclient.ManagedItem) error { + details := formatDownloadListCaption(item) + if item.ImageURL != "" { + asset, err := client.FetchImage(ctx.Ctx, item) + if err != nil { + ctx.Log.Err(err).Int64("item_id", item.ID).Msg("Failed to fetch poster for Matrix listing") + } else if asset != nil { + if err := ctx.SendImage(asset, details); err != nil { + ctx.Log.Err(err).Int64("item_id", item.ID).Msg("Failed to upload poster for Matrix listing") + } else { + return nil + } + } else { + ctx.Log.Debug().Int64("item_id", item.ID).Msg("Poster was empty for Matrix listing") + } + } + ctx.Reply(details) + return nil +} + +func formatDownloadListCaption(item arrclient.ManagedItem) string { + return fmt.Sprintf("%s %s", monitoredIcon(item.Monitored), arrclient.FormatManagedItem(item)) +} + +func formatDownloadListFallbackCard(item arrclient.ManagedItem) string { + return formatDownloadListCaption(item) +} + +func monitoredIcon(monitored bool) string { + if monitored { + return "πŸ‘" + } + return "🚫" +} diff --git a/packages/arrtrix/pkg/matrixcmd/download_test.go b/packages/arrtrix/pkg/matrixcmd/download_test.go new file mode 100644 index 0000000..19b93b9 --- /dev/null +++ b/packages/arrtrix/pkg/matrixcmd/download_test.go @@ -0,0 +1,44 @@ +package matrixcmd + +import ( + "testing" + + "sneeuwvlok/packages/arrtrix/pkg/arrclient" +) + +func TestFormatDownloadListFallbackCardUsesMonitoredIcon(t *testing.T) { + item := arrclient.ManagedItem{ + ID: 1, + Title: "Severance", + Year: 2022, + Monitored: true, + } + + fallback := formatDownloadListFallbackCard(item) + if fallback != "πŸ‘ Severance (2022)" { + t.Fatalf("unexpected monitored fallback %q", fallback) + } +} + +func TestFormatDownloadListFallbackCardUsesUnmonitoredIcon(t *testing.T) { + item := arrclient.ManagedItem{ + ID: 7, + Title: "Andor", + Year: 2022, + Monitored: false, + } + + fallback := formatDownloadListFallbackCard(item) + if fallback != "🚫 Andor (2022)" { + t.Fatalf("unexpected unmonitored fallback %q", fallback) + } +} + +func TestMonitoredIcon(t *testing.T) { + if monitoredIcon(true) != "πŸ‘" { + t.Fatalf("expected monitored icon, got %q", monitoredIcon(true)) + } + if monitoredIcon(false) != "🚫" { + t.Fatalf("expected unmonitored icon, got %q", monitoredIcon(false)) + } +} diff --git a/packages/arrtrix/pkg/matrixcmd/processor.go b/packages/arrtrix/pkg/matrixcmd/processor.go index 78915ea..e9d3980 100644 --- a/packages/arrtrix/pkg/matrixcmd/processor.go +++ b/packages/arrtrix/pkg/matrixcmd/processor.go @@ -18,6 +18,7 @@ import ( "maunium.net/go/mautrix/format" "maunium.net/go/mautrix/id" + "sneeuwvlok/packages/arrtrix/pkg/arrclient" "sneeuwvlok/packages/arrtrix/pkg/observability" ) @@ -221,7 +222,49 @@ func (c *Context) Reply(message string, args ...any) { content := format.RenderMarkdown(message, true, false) content.MsgType = event.MsgNotice - if _, err := c.Bot.SendMessage(c.Ctx, c.OrigRoomID, event.EventMessage, &event.Content{Parsed: &content}, nil); err != nil { + if err := c.sendNotice(&content); err != nil { c.Log.Err(err).Msg("Failed to reply to Matrix room command") } } + +func (c *Context) ReplyFormatted(body, formattedBody string) { + content := &event.MessageEventContent{ + MsgType: event.MsgNotice, + Body: body, + Format: event.FormatHTML, + FormattedBody: formattedBody, + } + if err := c.sendNotice(content); err != nil { + c.Log.Err(err).Msg("Failed to reply to Matrix room command") + } +} + +func (c *Context) SendImage(asset *arrclient.MediaAsset, body string) error { + if asset == nil || len(asset.Data) == 0 { + return nil + } + + mxcURL, file, err := c.Bot.UploadMedia(c.Ctx, c.OrigRoomID, asset.Data, asset.FileName, asset.MimeType) + if err != nil { + return err + } + + content := &event.MessageEventContent{ + MsgType: event.MsgImage, + Body: body, + FileName: asset.FileName, + URL: mxcURL, + File: file, + Info: &event.FileInfo{ + MimeType: asset.MimeType, + Size: len(asset.Data), + }, + } + _, err = c.Bot.SendMessage(c.Ctx, c.OrigRoomID, event.EventMessage, &event.Content{Parsed: content}, nil) + return err +} + +func (c *Context) sendNotice(content *event.MessageEventContent) error { + _, err := c.Bot.SendMessage(c.Ctx, c.OrigRoomID, event.EventMessage, &event.Content{Parsed: content}, nil) + return err +}