commit d5814f32ec8c92cab339f1ede6dfdf538fb4d556
Author: Katja Ramona Sophie Kwast (zaphyra) <git@zaphyra.eu>
Date: Wed, 20 May 2026 12:29:16 +0200
Author: Katja Ramona Sophie Kwast (zaphyra) <git@zaphyra.eu>
Date: Wed, 20 May 2026 12:29:16 +0200
initial commit
207 files changed, 15876 insertions(+), 0 deletions(-)
A
|
77
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
178
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
200
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
103
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
78
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
132
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
121
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
91
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
315
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
59
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
167
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
94
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
185
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
103
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
77
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
241
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
248
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
74
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
150
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
133
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
148
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
178
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
155
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
199
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
208
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
156
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
92
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
266
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
156
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
140
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
74
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
226
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
180
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
136
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
75
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
53
+++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
103
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
385
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
82
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
53
+++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
194
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
216
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
69
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
65
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
58
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
121
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
220
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
94
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
81
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
141
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
75
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
227
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
144
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
76
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
124
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
122
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
73
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
133
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
187
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
185
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
94
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
273
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
212
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
82
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
95
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
55
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
82
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
204
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
66
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
107
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
73
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
109
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
249
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
297
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
577
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
69
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
65
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
40
++++++++++++++++++++++++++++++++++++++++
A
|
142
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
80
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
108
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
77
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
50
++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
79
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
80
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
diff --git a/.sops.yaml b/.sops.yaml @@ -0,0 +1,72 @@ +keys: + - &zaphyra BFE6386C8D66BCD4DAE14FC895F0FE7CD7E6A022 + - &cautus age1yln5qratl82u6sqakh9gn2c0s88nzm62cw9muq5z4kzgp9wqpyzshhptq9 + - &sorrah age102glvtt2nwx4ymgjh85449exlhmzk56cayy9hkkpl82u6wm64qzqjar7ju + - &isodon age10ujnqv3ttdw23f7jryjg4dtw83fj2wf275kv38f2qrtj5kk3hg4qf5uf3h + - &cuvier age1wztgmu7rm4yvdr0x8xucwfmnf0sruafwjx2ttl9cvg54epn0qysqqnr5n3 + - &clevai age1q58tmhk5hxrfn86qymet3mznafdzuhwl47s8mt3nyn43pararc5sng7kag + - &leucas age1enkp0mlswl30s4h7z4qvyha4cmc2n2exs0v97276q5mx0jc86ggs7g2dyq + +creation_rules: + - path_regex: secrets/common\.yaml$ + key_groups: + - age: + - *cautus + - *sorrah + - *isodon + - *cuvier + - *clevai + - *leucas + pgp: + - *zaphyra + + - path_regex: secrets/cautus\.yaml$ + key_groups: + - age: + - *cautus + pgp: + - *zaphyra + + - path_regex: secrets/sorrah\.yaml$ + key_groups: + - age: + - *sorrah + pgp: + - *zaphyra + + - path_regex: secrets/isodon\.yaml$ + key_groups: + - age: + - *isodon + pgp: + - *zaphyra + - path_regex: secrets/cuvier\.yaml$ + key_groups: + - age: + - *cuvier + pgp: + - *zaphyra + - path_regex: secrets/clevai\.yaml$ + key_groups: + - age: + - *clevai + pgp: + - *zaphyra + - path_regex: secrets/leucas\.yaml$ + key_groups: + - age: + - *leucas + pgp: + - *zaphyra + - path_regex: secrets/zaphyra/sieve\.yaml$ + key_groups: + - age: + - *cautus + pgp: + - *zaphyra + - path_regex: secrets/zaphyra/floractl\.yaml$ + key_groups: + - age: + - *cuvier + pgp: + - *zaphyra
diff --git a/default.nix b/default.nix @@ -0,0 +1,69 @@ +let + npins = import ./npins; + overlays = import ./overlays { inherit npins; }; + nixosConfigurations = import ./machines { inherit lib npins; }; + lib = (import "${npins.nixpkgs}/lib").extend ( + final: prev: { + zpha = import ./lib { + inherit npins; + lib = prev; + }; + } + ); + +in +{ + + inherit + overlays + nixosConfigurations + ; + + nixosModules = import ./nixosModules { inherit lib; }; + + formatter = lib.zpha.forAllSystems { + inherit (npins) nixpkgs; + body = pkgs: pkgs.nixfmt-rfc-style; + }; + + packages = lib.zpha.forAllSystems { + overlays = overlays.default; + body = + pkgs: + lib.mergeAttrsList [ + (import ./packages pkgs) + (lib.flip lib.mapAttrs' nixosConfigurations ( + machineName: nixosConfiguration: + lib.nameValuePair "setupDisk-${machineName}" ( + pkgs.zpha.setupDisk.override { + systemConfig = nixosConfiguration.config; + } + ) + )) + ]; + }; + + deploy = { + activationTimeout = 600; + confirmTimeout = 240; + nodes = lib.flip builtins.mapAttrs nixosConfigurations ( + _: nixosConfiguration: + let + inherit (nixosConfiguration) config pkgs; + in + { + hostname = config.networking.fqdn; + sshUser = "root"; + sshOpts = [ + "-p" + (toString (lib.head config.services.openssh.ports)) + ]; + profiles.system = { + user = "root"; + path = pkgs.zpha.deployrsActivator.override { systemConfig = config; }; + }; + } + ); + }; + +}
diff --git a/flake.lock b/flake.lock @@ -0,0 +1,5 @@ +{ + "nodes": { "root": {} }, + "root": "root", + "version": 7 +}
diff --git a/flake.nix b/flake.nix @@ -0,0 +1 @@ +{ outputs = _: import ./.; }
diff --git a/lib/collectModules.nix b/lib/collectModules.nix @@ -0,0 +1,77 @@ +{ ... }: + +{ + + collectModules = + { + path, + scope ? { }, + fileName ? "default.nix", + }: + ( + let + #some helpers (mostly from nixpkgs or at least derived from that) + sublist = + start: count: list: + let + len = builtins.length list; + in + builtins.genList (n: builtins.elemAt list (n + start)) ( + if start >= len then + 0 + else if start + count > len then + len - start + else + count + ); + pipe = builtins.foldl' (x: f: f x); + flatten = x: if builtins.isList x then builtins.concatMap (y: flatten y) x else [ x ]; + drop = count: list: sublist count (builtins.length list) list; + splitPath = path: builtins.filter builtins.isString (builtins.split "/" (builtins.toString path)); + + #actual logic starts here + fileNameLength = builtins.stringLength fileName; + basePathLength = builtins.length (splitPath ./.); + + collectFiles = + path: + pipe (builtins.readDir path) [ + (builtins.mapAttrs ( + name: value: + if value == "directory" then (collectFiles (path + "/${name}")) else (path + "/${name}") + )) + builtins.attrValues + flatten + (builtins.filter ( + elem: + let + pathLength = builtins.length (splitPath elem); + in + ( + ( + let + strLength = builtins.stringLength elem; + in + builtins.substring (strLength - fileNameLength) strLength elem + ) == fileName + ) + && (pathLength - 1) != basePathLength + )) + ]; + + in + pipe (collectFiles path) [ + (builtins.map (filePath: { + name = ( + let + pathParts = drop basePathLength (splitPath filePath); + in + builtins.concatStringsSep "-" (sublist 0 ((builtins.length pathParts) - 1) pathParts) + ); + value = import filePath scope; + })) + builtins.listToAttrs + ] + ); + +}
diff --git a/lib/default.nix b/lib/default.nix @@ -0,0 +1,27 @@ +{ + lib, + npins, + ... +}: + +( + let + # read the current directorys files and pipe the result through a list of functions + selfLib = lib.pipe (builtins.readDir ./.) [ + # convert to a list containing just the attribute names + (builtins.attrNames) + # drop "default.nix" from the list + (builtins.filter (name: name != "default.nix")) + # convert each list-element containing its file-name to an element containing the file's content + (builtins.map ( + name: + import ./${name} { + inherit npins lib selfLib; + } + )) + # merge list of attribute sets together + (lib.mergeAttrsList) + ]; + in + selfLib +)
diff --git a/lib/forAllSystems.nix b/lib/forAllSystems.nix @@ -0,0 +1,28 @@ +{ + npins, + lib, + ... +}: + +{ + + forAllSystems = + { + body, + systems ? [ + "x86_64-linux" + "aarch64-linux" + ], + nixpkgs ? npins.nixpkgs, + overlays ? [ ], + }: + lib.genAttrs systems ( + system: + body ( + import nixpkgs { + inherit system overlays; + } + ) + ); + +}
diff --git a/lib/mergeAttrsRecursiveList.nix b/lib/mergeAttrsRecursiveList.nix @@ -0,0 +1,31 @@ +{ + lib, + ... +}: + +{ + + mergeAttrsRecursiveList = + list: + let + # `binaryMerge start end` merges the elements at indices `index` of `list` such that `start <= index < end` + # Type: Int -> Int -> AttrSet + binaryMerge = + start: end: + # assert start < end; # Invariant + if end - start >= 2 then + # If there's at least 2 elements, split the range in two, recurse on each part and merge the result + # The invariant is satisfied because each half will have at least 1 element + lib.recursiveUpdate (binaryMerge start (start + (end - start) / 2)) ( + binaryMerge (start + (end - start) / 2) end + ) + else + # Otherwise there will be exactly 1 element due to the invariant, in which case we just return it directly + lib.elemAt list start; + in + if list == [ ] then + # Calling binaryMerge as below would not satisfy its invariant + { } + else + binaryMerge 0 (builtins.length list); +}
diff --git a/lib/toCamelCase.nix b/lib/toCamelCase.nix @@ -0,0 +1,35 @@ +{ + lib, + ... +}: + +{ + + toCamelCase = + str: + lib.throwIfNot (lib.isString str) + "toCamelCase does only accepts string values, but got ${lib.typeOf str}" + ( + let + separators = lib.splitStringBy ( + prev: curr: + builtins.elem curr [ + "-" + "_" + " " + ] + ) false str; + + parts = lib.flatten ( + map (lib.splitStringBy ( + prev: curr: lib.match "[a-z]" prev != null && lib.match "[A-Z]" curr != null + ) true) separators + ); + + first = if lib.length parts > 0 then lib.toLower (lib.head parts) else ""; + rest = if lib.length parts > 1 then builtins.map lib.toSentenceCase (lib.tail parts) else [ ]; + in + lib.concatStrings (map (lib.addContextFrom str) ([ first ] ++ rest)) + ); + +}
diff --git a/machines/cautus.nix b/machines/cautus.nix @@ -0,0 +1,178 @@ +{ + + system = "x86_64-linux"; + nixpkgsStable = true; + + id = 2; + domain = "fc9f.de"; + + sshPublicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFfZ86/2bpfOY6G2pcEWPlmIS7Mf47lG+s+lmaD/ZL0Z"; + wgPublicKey = "pLPU3QizJjY7QkCItenm/zRKKx5chCj5dTHBLtWHFQs="; + + hardware = { + cpuVendor = "intel"; + allowHibernation = false; + }; + + networking = { + ip4IsPrivate = false; + ip4Address = "91.132.144.50"; + ip4PrefixLength = 22; + defaultGateway4 = "91.132.144.1"; + + ip6IsPrivate = false; + ip6Address = "2a03:4000:37:65f::1"; + ip6PrefixLength = 64; + + dn42 = { + ip6Address = "fd6b:6174:6a61::2"; + ip6PrefixLength = 128; + }; + }; + + nixosConfiguration = + { + machines, + machineConfig, + config, + lib, + ... + }: + { + sops.secrets = { + wgPrivateKey = { + owner = "systemd-network"; + group = "systemd-network"; + }; + }; + + common = { + profiles.netcup.enable = true; + configure = { + primaryNetworkInterface.enable = true; + rootDisk.swap = { + enable = true; + size = "2G"; + }; + }; + }; + + zpha = { + configure = { + dnsServer.enable = true; + xmppServer.enable = true; + mailServer.enable = true; + matrixBridges.enable = true; + }; + profiles = { + zaphyra.enable = true; + dn42 = { + enable = true; + addresses = [ + "${machineConfig.networking.dn42.ip6Address}/${toString machineConfig.networking.dn42.ip6PrefixLength}" + "fd6b:6174:6a61:53::${toString machineConfig.id}/128" + ]; + }; + }; + + websites = { + "zaphyra.eu".enable = true; + "oeffi.zaphyra.eu".enable = true; + "notes.zaphyra.eu".enable = true; + "dav.zaphyra.eu".enable = true; + "continuwuity.zaphyra.eu".enable = true; + "vault.zaphyra.eu".enable = true; + "git.zaphyra.eu".enable = true; + "bikemap.zaphyra.eu".enable = true; + "gts.zaphyra.eu".enable = true; + }; + }; + + services.babeld = { + enable = true; + interfaces = { + wg-sorrah = { }; + }; + interfaceDefaults = { + type = "tunnel"; + }; + extraConfig = '' + local-port-readwrite 33123 + + export-table 254 # main + import-table 255 # local + + kernel-priority 1000 + + # allow own prefixes + in ip fd6b:6174:6a61::/48 allow + + # allow external routes + in ip fd00::/8 allow + + in deny + + redistribute ip fd6b:6174:6a61::${toString machineConfig.id} eq 48 allow + redistribute local deny + redistribute deny + ''; + }; + + networking.firewall = { + checkReversePath = "loose"; + trustedInterfaces = [ "wg-sorrah" ]; + allowedUDPPorts = [ + config.systemd.network.netdevs."42-wg-sorrah".wireguardConfig.ListenPort + ]; + }; + + systemd.network = { + config.networkConfig = { + IPv6Forwarding = true; + }; + + netdevs = { + "42-wg-sorrah" = { + netdevConfig = { + Kind = "wireguard"; + Name = "wg-sorrah"; + MTUBytes = 1280; + }; + + wireguardConfig = { + PrivateKeyFile = config.sops.secrets."wgPrivateKey".path; + ListenPort = 4200 + machineConfig.id; + FirewallMark = 4200 + machineConfig.id; + }; + + wireguardPeers = lib.singleton { + PublicKey = machines.sorrah.wgPublicKey; + AllowedIPs = lib.singleton "::/0"; + Endpoint = "[${machines.sorrah.networking.ip6Address}]:${toString (4200 + machines.sorrah.id)}"; + PersistentKeepalive = 10; + }; + }; + }; + + networks = { + "42-wg-sorrah" = { + matchConfig.Name = "wg-sorrah"; + linkConfig = { + RequiredForOnline = false; + Multicast = true; + }; + networkConfig = { + DHCP = false; + IPv6AcceptRA = false; + }; + address = [ + "fe80:fc9f::${toString machineConfig.id}/64" + "fd6b:6174:6a61:fc9f::${toString machineConfig.id}/48" + ]; + }; + }; + }; + + }; + +}
diff --git a/machines/clevai.nix b/machines/clevai.nix @@ -0,0 +1,71 @@ +{ + + system = "x86_64-linux"; + nixpkgsStable = true; + + id = 5; + domain = "fc9f.de"; + + sshPublicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH+aZ4jTRDA9Bf9i8UBdGzucahtpDZbjtyaTfR5BE9j4"; + wgPublicKey = "yP1kDJLP4YmHjDLKlOu7bwyd7wCkPHHAx4tY28b/Mms="; + + hardware = { + cpuVendor = "intel"; + allowHibernation = false; + }; + + networking = { + ip4IsPrivate = false; + ip4Address = "152.89.106.158"; + ip4PrefixLength = 22; + defaultGateway4 = "152.89.104.1"; + + ip6IsPrivate = false; + ip6Address = "2a03:4000:39:e9a::1"; + ip6PrefixLength = 64; + defaultGateway6 = "fe80::1"; + + dn42 = { + ip6Address = "fd6b:6174:6a61::5"; + ip6PrefixLength = 128; + }; + }; + + nixosConfiguration = + { + machineConfig, + ... + }: + { + + sops.secrets = { + wgPrivateKey = { + owner = "systemd-network"; + group = "systemd-network"; + }; + }; + + common = { + profiles.netcup.enable = true; + configure = { + primaryNetworkInterface.enable = true; + rootDisk.swap = { + enable = true; + size = "2G"; + }; + }; + }; + + zpha.profiles = { + zaphyra.enable = true; + dn42 = { + enable = true; + addresses = [ + "${machineConfig.networking.dn42.ip6Address}/${toString machineConfig.networking.dn42.ip6PrefixLength}" + ]; + }; + }; + + }; + +}
diff --git a/machines/cuvier.nix b/machines/cuvier.nix @@ -0,0 +1,200 @@ +{ + + system = "x86_64-linux"; + nixpkgsStable = true; + + id = 4; + domain = "fc9f.de"; + + sshPubKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIANWOi6NMsfZ8wXOj/DXc092yajzG3VjPfRE0M9pViGh"; + wgPublicKey = "CdnaBQL7c7zX0ORKhYyXp3HZ1kjqoEIGW03/mCCwAgI="; + syncthingId = "JUIJQZE-AWVYZIZ-CR6E66M-TAJIRDH-NEPEPZW-FHO37MJ-QM2MV5Q-OFJTEQI"; + + hardware = { + cpuVendor = "amd"; + allowHibernation = false; + }; + + networking = { + primaryInterface = "enp3s0f0"; + + ip4IsPrivate = true; + ip4Address = "192.168.2.110"; + ip4PrefixLength = 24; + defaultGateway4 = "192.168.2.1"; + + ip6IsPrivate = false; + ip6Address = "2a03:4000:4d:5e:acab::4"; + ip6PrefixLength = 128; + + dn42 = { + ip6Address = "fd6b:6174:6a61::4"; + ip6PrefixLength = 128; + }; + }; + + nixosConfiguration = + { lib, pkgs, ... }: + { + boot.initrd.systemd.emergencyAccess = true; + boot.initrd.availableKernelModules = [ + "ehci_pci" + "xhci_pci" + "ahci" + "usbhid" + "usb_storage" + "sd_mod" + "uas" + ]; + + boot.initrd.luks.devices = { + data.device = "/dev/disk/by-partlabel/cuvier-data"; + backup.device = "/dev/disk/by-partlabel/cuvier-backup"; + }; + + systemd.tmpfiles.settings."mounts" = { + "/mnt/music".d = { + mode = "0755"; + user = "zaphyra"; + group = "users"; + }; + "/mnt/syncthing".d = { + mode = "0750"; + user = "zaphyra"; + group = "users"; + }; + "/mnt/restic".d = { + mode = "0700"; + user = "restic"; + group = "restic"; + }; + }; + + fileSystems = { + "/mnt/music" = { + device = "/dev/mapper/data"; + options = [ + "subvol=music" + "compress=zstd" + "discard=async" + ]; + }; + "/mnt/syncthing" = { + device = "/dev/mapper/data"; + options = [ + "subvol=syncthing" + "compress=zstd" + "discard=async" + ]; + }; + "/mnt/restic" = { + device = "/dev/mapper/backup"; + options = [ + "subvol=restic" + "discard=async" + ]; + }; + }; + + services.syncthing.settings.folders.zaphyra-music-orig.path = "/mnt/music"; + + sops.secrets = { + wgPrivateKey = { + owner = "systemd-network"; + group = "systemd-network"; + }; + }; + + systemd.services.docker.path = [ pkgs.nftables ]; + security.lockKernelModules = lib.mkForce false; + networking.firewall.trustedInterfaces = [ + "docker0" + "br-88669f4be391" + ]; + virtualisation.docker = { + enable = true; + package = pkgs.docker_29; + storageDriver = "btrfs"; + daemon.settings = { + firewall-backend = "nftables"; + data-root = "/persist/system/var/lib/docker"; + }; + }; + + users.users.zaphyra.extraGroups = [ "docker" ]; + + services.auto-cpufreq.enable = true; + + common = { + profiles = { + nvme.enable = true; + }; + security = { + kernel.enable = false; + }; + configure = { + primaryNetworkInterface = { + enable = true; + acceptRouterAdvertisements = true; + ip6Address = null; + }; + rootDisk.swap = { + enable = true; + size = "2G"; + }; + }; + }; + + zpha = { + websites = { + "ctu.cx".enable = true; + "fedi.ctu.cx".enable = true; + "hass.zaphyra.eu".enable = true; + "gomuks.zaphyra.eu".enable = true; + "things.zaphyra.eu".enable = true; + "music.zaphyra.eu".enable = true; + "memories.zaphyra.eu".enable = true; + "links.zaphyra.eu".enable = true; + }; + configure = { + netcupTunnel.enable = true; + syncthing.enable = true; + floraCtl.enable = true; + syncthingBackup.enable = true; + }; + profiles = { + zaphyra.enable = true; + dn42.enable = true; + resticBackupTarget = { + enable = true; + path = "/mnt/restic"; + keys = { + cuvier-syncthing-zaphyra-zaphyra-db-richtlinien = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOuRCzv8FO3EaUY9R36cg7RNRaRsMNEdxbUAMrU8hXXX"; + cuvier-syncthing-zaphyra-documents = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPu2w5POF//j3FWQJ/h5+Upal9hqe4ytDWUX6Nsoow+1"; + cuvier-syncthing-zaphyra-pictures = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILZCB0e1RLyEJlu7gk45R7Y35Kal5VXOCGE+gYvVm1A+"; + cuvier-syncthing-zaphyra-videos = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIPbM811C0WvdN+19JBm3ulyb0SMoYIhT+GLU2pXPiEA"; + cuvier-syncthing-zaphyra-audiobooks = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE6+dtNnZuVqnP7Get7pQWTPRrgf4H+L/d4I69y9+M40"; + cuvier-syncthing-zaphyra-music-orig = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJAAvOyzZz6YgbUuYRFZH1oHbboVUAXQM0hSTXJYFFwA"; + cuvier-syncthing-zaphyra-media = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAjf9rztvIBVjtuJIMaSCo8nIZuLNjrlA5NoNH/0S+YK"; + cuvier-navidrome = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP0vCwn4H04RqiLFUVK06N1ZOhEvNgdBod1Eedu82LHP"; + cuvier-things = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG9ME2e9a2BbFgTxVY5OSL0VupYxZ10SjcLeO27qBBIF"; + cuvier-gotosocial = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOvl23IonsUnU5nDVMjNp0W56HcT1TTuXRvFrkOay0iM"; + cuvier-immich = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA8a7B0GzYf1J1Of8iwSMFQUON6CWCwnJW94K3uX95ij"; + isodon-gotosocial = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID85l2Im1ff0JGp8vH8IngnjKB3K/cFiur/grPxWJbUE"; + isodon-navidrome = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMv54uqcNCmcbYWzA1mGPeKrh29E2+/sr08caX3jihQc"; + cautus-gotosocial = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIADOmcKGelaxzS9oObFMOdfUdm/PWS6Og9IJFZlLrsvm"; + cautus-memos = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDJZ4Y87FvE9ejTh2X01u73+iUYAbSxHz0SzgQ/oMW7W"; + cautus-continuwuity = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICnwja11WL5lX7uUnuapINM5NydD9reJ1N6uIR8IrUnO"; + cautus-radicale = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK98eqmXaUpbm6PrRi/n2WmQ+Oo0x8z/JQathF8OPzNU"; + cautus-prosody = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDe5En9owlKsj15sgWoq0zOFuAfc7VX3ON5DJ8TPIXAP"; + cautus-vaultwarden = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJrsALG/N1wAA9T4MzMkbdA8LKNSsi38I4AsrmKi3eN2"; + cautus-gitolite = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKrxAUyM6a7Rw1vjK+OyOR+9bYrWfV3L7bu05w9IPG+h"; + cautus-mailserver = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGsKAXf8yLKv+B8FIrIssxlryqRPtuajPLJ7hVRh0dbz"; + }; + }; + }; + + }; + }; + +}
diff --git a/machines/default.nix b/machines/default.nix @@ -0,0 +1,58 @@ +{ + lib ? import "${(import ../npins).nixpkgs}/lib", + npins ? import ../npins, + ... +}: + +( + let + # read the current directorys files and pipe the result through a list of functions + machines = builtins.listToAttrs ( + lib.pipe (builtins.readDir ./.) [ + # convert to a list containing just the attribute names + (builtins.attrNames) + # drop "default.nix" from the list + (builtins.filter (name: name != "default.nix")) + # convert each list-element containing its file-name to an element containing the file's content + (builtins.map (name: { + # remove '.nix' suffix + name = lib.removeSuffix ".nix" name; + value = import ./${name}; + })) + ] + ); + + nixosModules = import ../nixosModules { inherit lib; }; + overlays = import ../overlays { inherit npins; }; + + specialArgs = { + inherit npins machines nixosConfigurations; + resources = import ../resources { inherit npins lib; }; + sopsSecrets = import ../secrets { inherit npins lib; }; + }; + + nixosConfigurations = lib.flip builtins.mapAttrs machines ( + machineName: machineConfig: + let + nixpkgs = if !machineConfig.nixpkgsStable then npins.nixpkgsUnstable else npins.nixpkgs; + in + import "${nixpkgs}/nixos/lib/eval-config.nix" { + system = machineConfig.system; + + specialArgs = specialArgs // { + machineConfig = machineConfig // { + inherit machineName; + }; + }; + + modules = lib.flatten [ + { nixpkgs.overlays = overlays.default; } + nixosModules.default + machineConfig.nixosConfiguration + ]; + } + ); + + in + nixosConfigurations +)
diff --git a/machines/isodon.nix b/machines/isodon.nix @@ -0,0 +1,103 @@ +{ + + system = "x86_64-linux"; + nixpkgsStable = true; + + id = 3; + domain = "fc9f.de"; + + sshPubKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG/7udhktYVZHHZ2RXQeKHt0ACfcG5dmTpyd5LMw4U0O root@nixos"; + wgPublicKey = "nvyhYuWJl/dKyV/2+bDrUisvL3mi38PsNzfdIDDwSjY="; + syncthingId = "QI2EPUE-4VMZ3XV-LXX3GXP-RHCWTRY-AACLSGL-YG7MIYV-THST74N-KJGIBQ6"; + + hardware = { + cpuVendor = "intel"; + allowHibernation = false; + }; + + networking = { + primaryInterface = "enp1s0"; + + ip4IsPrivate = true; + ip4Address = "192.168.2.111"; + ip4PrefixLength = 24; + defaultGateway4 = "192.168.2.1"; + + ip6IsPrivate = false; + ip6Address = "2a03:4000:4d:5e:acab::3"; + ip6PrefixLength = 128; + + dn42 = { + ip6Address = "fd6b:6174:6a61::3"; + ip6PrefixLength = 128; + }; + }; + + nixosConfiguration = _: { + boot.initrd.systemd.emergencyAccess = true; + boot.kernel.sysctl."net.ipv6.conf.all.proxy_ndp" = true; + + sops.secrets = { + wgPrivateKey = { + owner = "systemd-network"; + group = "systemd-network"; + }; + }; + + common = { + profiles.nvme.enable = true; + configure = { + primaryNetworkInterface = { + enable = true; + acceptRouterAdvertisements = true; + ip6Address = null; + }; + rootDisk.swap = { + enable = true; + size = "2G"; + }; + }; + }; + + zpha = { + websites = { + "fedi.home.ctu.cx".enable = true; + "music.zaphyra.eu" = { + enable = true; + subdomain = "music2"; + }; + }; + + configure = { + syncthing.enable = true; + netcupTunnel.enable = true; + }; + + profiles = { + zaphyra.enable = true; + dn42.enable = true; + resticBackupTarget = { + enable = true; + path = "/persist/system/restic-backups"; + keys = { + cuvier-navidrome = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP0vCwn4H04RqiLFUVK06N1ZOhEvNgdBod1Eedu82LHP"; + cuvier-things = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG9ME2e9a2BbFgTxVY5OSL0VupYxZ10SjcLeO27qBBIF"; + cuvier-immich = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA8a7B0GzYf1J1Of8iwSMFQUON6CWCwnJW94K3uX95ij"; + cuvier-gotosocial = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOvl23IonsUnU5nDVMjNp0W56HcT1TTuXRvFrkOay0iM"; + isodon-gotosocial = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID85l2Im1ff0JGp8vH8IngnjKB3K/cFiur/grPxWJbUE"; + isodon-navidrome = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMv54uqcNCmcbYWzA1mGPeKrh29E2+/sr08caX3jihQc"; + cautus-gotosocial = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIADOmcKGelaxzS9oObFMOdfUdm/PWS6Og9IJFZlLrsvm"; + cautus-memos = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDJZ4Y87FvE9ejTh2X01u73+iUYAbSxHz0SzgQ/oMW7W"; + cautus-continuwuity = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICnwja11WL5lX7uUnuapINM5NydD9reJ1N6uIR8IrUnO"; + cautus-radicale = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK98eqmXaUpbm6PrRi/n2WmQ+Oo0x8z/JQathF8OPzNU"; + cautus-prosody = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDe5En9owlKsj15sgWoq0zOFuAfc7VX3ON5DJ8TPIXAP"; + cautus-vaultwarden = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJrsALG/N1wAA9T4MzMkbdA8LKNSsi38I4AsrmKi3eN2"; + cautus-gitolite = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKrxAUyM6a7Rw1vjK+OyOR+9bYrWfV3L7bu05w9IPG+h"; + cautus-mailserver = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGsKAXf8yLKv+B8FIrIssxlryqRPtuajPLJ7hVRh0dbz"; + }; + }; + }; + }; + }; + +}
diff --git a/machines/leucas.nix b/machines/leucas.nix @@ -0,0 +1,71 @@ +{ + + system = "x86_64-linux"; + nixpkgsStable = true; + + id = 6; + domain = "fc9f.de"; + + sshPublicKey = ""; + wgPublicKey = ""; + + hardware = { + cpuVendor = "intel"; + allowHibernation = false; + }; + + networking = { + dn42 = { + ip6Address = "fd6b:6174:6a61::6"; + ip6PrefixLength = 128; + }; + }; + + nixosConfiguration = _: { + sops.secrets = { + wgPrivateKey = { + owner = "systemd-network"; + group = "systemd-network"; + }; + }; + + common = { + configure.rootDisk.swap = { + enable = true; + size = "16G"; + }; + + profiles = { + nvme.enable = true; + }; + + programs = { + shellUtilities.enable = true; + nixUtilities.enable = true; + }; + + services = { + greetd.autoLogin = { + enable = true; + user = "zaphyra"; + }; + }; + }; + + zpha = { + configure = { + cccdaWifi.enable = true; + }; + + profiles = { + zaphyra.enable = true; + graphical.enable = true; + }; + + services = { + keyd.enable = true; + }; + }; + }; + +}
diff --git a/machines/sorrah.nix b/machines/sorrah.nix @@ -0,0 +1,78 @@ +{ + + system = "x86_64-linux"; + nixpkgsStable = true; + + id = 1; + domain = "fc9f.de"; + + sshPublicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG/7udhktYVZHHZ2RXQeKHt0ACfcG5dmTpyd5LMw4U0O root@nixos"; + wgPublicKey = "J+kRRNU65JGc0yk04v6P3tFwHSQOIfq8EkfD2gFupg4="; + + hardware = { + cpuVendor = "intel"; + allowHibernation = false; + }; + + networking = { + ip4IsPrivate = false; + ip4Address = "194.36.145.49"; + ip4PrefixLength = 22; + defaultGateway4 = "194.36.144.1"; + + ip6IsPrivate = false; + ip6Address = "2a03:4000:4d:5e::1"; + ip6PrefixLength = 64; + + dn42 = { + wgPublicKey = "MRXPP//j+BDCiUyrYHdXtdULAsCZyfgumas8pxp6oiE="; + ip6Address = "fd6b:6174:6a61::1"; + ip6PrefixLength = 48; + }; + }; + + nixosConfiguration = + { lib, ... }: + { + + boot.initrd.systemd.emergencyAccess = true; + boot.kernel.sysctl."net.ipv6.conf.all.proxy_ndp" = true; + + sops.secrets = { + wgPrivateKey = { + owner = "systemd-network"; + group = "systemd-network"; + }; + }; + + common = { + profiles.netcup.enable = true; + configure = { + primaryNetworkInterface.enable = true; + rootDisk.swap = { + enable = true; + size = "2G"; + }; + }; + }; + + zpha = { + profiles.zaphyra.enable = true; + + websites = { + "flauschehorn.zaphyra.eu".enable = true; + "ip.fc9f.de".enable = true; + }; + + configure = { + dnsServer.enable = true; + dn42Router.enable = true; + netcupTunnel = { + enable = true; + addresses = lib.singleton "2a03:4000:4d:5e:acab::1/112"; + }; + }; + }; + }; + +}
diff --git a/maidModules/dbus.nix b/maidModules/dbus.nix @@ -0,0 +1,25 @@ +{ + config, + pkgs, + lib, + ... +}: + +{ + + options.dbus.packages = lib.mkOption { + type = with lib.types; listOf package; + default = [ ]; + }; + + config.file.xdg_data = lib.mkIf (config.dbus.packages != [ ]) { + "dbus-1/services" = { + source = pkgs.symlinkJoin { + name = "user-dbus-services"; + paths = config.dbus.packages; + stripPrefix = "/share/dbus-1/services"; + }; + }; + }; + +}
diff --git a/maidModules/environment.nix b/maidModules/environment.nix @@ -0,0 +1,36 @@ +{ + config, + lib, + pkgs, + ... +}: +let + inherit (lib) types; + cfg = config.environment; + +in +{ + + options.environment = { + enable = lib.mkEnableOption "environment-related settings"; + sessionVariables = lib.mkOption { + type = types.attrsOf types.str; + default = { }; + }; + }; + + config = lib.mkIf cfg.enable { + file.home.".profile".text = '' + source <(${pkgs.systemd}/lib/systemd/user-environment-generators/30-systemd-environment-d-generator) + ''; + + file.xdg_config."environment.d/60-custom-session-vars.conf".text = + let + vars = cfg.sessionVariables; + varEntries = lib.attrsets.mapAttrsToList (k: v: "${k}=${v}") vars; + varFileContent = builtins.concatStringsSep "\n" varEntries; + in + varFileContent; + }; + +}
diff --git a/maidModules/programs/fish.nix b/maidModules/programs/fish.nix @@ -0,0 +1,132 @@ +{ + lib, + pkgs, + config, + ... +}: +let + inherit (lib) types; + cfg = config.programs.fish; + + toFishFunc = + body: funcName: + if (types.typeOf body) == "string" then + pkgs.writeFish "${funcName}.fish" '' + function ${funcName}; + ${lib.concatMapStringsSep "\n" (line: "\t${line}") (lib.splitString "\n" body)} + end + '' + else if lib.isDerivation body then + body + else + throw "Input is of invalid type ${types.typeOf body}, expected `path` or `string`."; + + isVendored = + plugin: + types.any (p: lib.pathExists "${plugin}/share/fish/${p}") [ + "vendor_conf.d" + "vendor_completions.d" + "vendor_functions.d" + ]; +in +{ + + options.programs.fish = { + enable = lib.mkEnableOption "fish"; + + config = lib.mkOption { + default = ""; + type = types.lines; + }; + + functions = lib.mkOption { + default = { }; + type = + with types; + attrsOf (oneOf [ + lines + path + ]); + }; + + earlyConfigFiles = lib.mkOption { + default = { }; + type = types.attrsOf types.lines; + }; + + abbrs = lib.mkOption { + default = { }; + type = types.attrsOf types.lines; + }; + + aliases = lib.mkOption { + default = { }; + type = types.attrsOf types.lines; + }; + + plugins = lib.mkOption { + type = types.attrsOf types.path; + default = { }; + }; + }; + + config = lib.mkIf cfg.enable { + programs.fish.earlyConfigFiles = lib.mapAttrs' ( + name: source: + lib.nameValuePair "plugin-${name}" + # fish + '' + # Plugin ${name} -- ${source} + set -l src "${source}" + + if test -d "$src/functions" + set fish_function_path $fish_function_path[1] "$src/functions" $fish_function_path[2..] + end + + if test -d "$src/completions" + set fish_complete_path $fish_complete_path[1] "$src/completions" $fish_complete_path[2..] + end + + for f in "$src/conf.d/"* + source "$f" + end + + if test -f "$src/key_bindings.fish" + source "$src/key_bindings.fish" + end + + if test -f "$src/init.fish" + source "$src/init.fish" + end + '' + ) (lib.filterAttrs (n: v: !(isVendored v)) cfg.plugins); + + file.xdg_config = { + "fish/config.fish" = lib.mkIf (cfg.config != "") { + source = pkgs.writers.writeFish "config.fish" cfg.config; + }; + "fish/conf.d/session-variables.fish" = lib.mkIf (config.environment.sessionVariables != { }) { + text = lib.concatMapAttrsStringSep "\n" ( + name: value: + "set --global --export ${lib.escapeShellArg name} ${lib.escapeShellArg (toString value)}" + ) config.environment.sessionVariables; + }; + "fish/conf.d/abbreviations.fish" = lib.mkIf (cfg.abbrs != { }) { + text = lib.concatMapAttrsStringSep "\n" ( + name: value: "abbr --add -- ${lib.escapeShellArg name} ${lib.escapeShellArg (toString value)}" + ) cfg.abbrs; + }; + "fish/conf.d/aliases.fish".text = lib.concatMapAttrsStringSep "\n" ( + name: value: "alias -- ${lib.escapeShellArg name} ${lib.escapeShellArg (toString value)}" + ) cfg.aliases; + } + // (lib.mapAttrs' ( + name: val: lib.nameValuePair "fish/functions/${name}.fish" { source = toFishFunc val name; } + ) cfg.functions) + // (lib.mapAttrs' ( + name: val: + lib.nameValuePair "fish/conf.d/${name}.fish" { source = pkgs.writers.writeFish "${name}.fish" val; } + ) cfg.earlyConfigFiles); + }; + +}
diff --git a/maidModules/programs/git.nix b/maidModules/programs/git.nix @@ -0,0 +1,36 @@ +{ + lib, + pkgs, + config, + ... +}: +let + inherit (lib) types; + cfg = config.programs.git; + settingsFormat = pkgs.formats.ini { }; + +in +{ + + options.programs.git = { + enable = lib.mkEnableOption "git"; + ignores = lib.mkOption { + default = { }; + type = with types; listOf str; + }; + settings = lib.mkOption { + default = { }; + type = settingsFormat.type; + }; + }; + + config = lib.mkIf cfg.enable { + packages = [ pkgs.git ]; + + file.xdg_config = { + "git/config".source = settingsFormat.generate "gitconfig.ini" cfg.settings; + "git/ignore".text = lib.concatStringsSep "\n" cfg.ignores; + }; + }; + +}
diff --git a/maidModules/programs/lazygit.nix b/maidModules/programs/lazygit.nix @@ -0,0 +1,34 @@ +{ + lib, + pkgs, + config, + ... +}: +let + inherit (lib) types; + cfg = config.programs.lazygit; + settingsFormat = pkgs.formats.yaml { }; + +in +{ + + options.programs.lazygit = { + enable = lib.mkEnableOption "lazygit"; + defaultShell = lib.mkOption { + type = types.bool; + default = true; + }; + settings = lib.mkOption { + default = { }; + type = settingsFormat.type; + }; + }; + + config = lib.mkIf cfg.enable { + packages = [ pkgs.lazygit ]; + + file.xdg_config."lazygit/config.yml".source = + settingsFormat.generate "lazygit-config.yaml" cfg.settings; + }; + +}
diff --git a/maidModules/programs/starship.nix b/maidModules/programs/starship.nix @@ -0,0 +1,51 @@ +{ + lib, + pkgs, + config, + ... +}: +let + inherit (lib) types; + cfg = config.programs.starship; + settingsFormat = pkgs.formats.toml { }; + +in +{ + + options.programs.starship = { + enable = lib.mkEnableOption "starship"; + settings = lib.mkOption { + type = settingsFormat.type; + default = { }; + }; + enableFishIntegration = lib.mkOption { + type = types.bool; + default = config.programs.fish.enable; + }; + enableTransience = lib.mkOption { + type = types.bool; + default = false; + }; + }; + + config = lib.mkIf cfg.enable { + packages = [ pkgs.starship ]; + + environment.sessionVariables.STARSHIP_CONFIG = lib.mkIf ( + cfg.settings != { } + ) "${cfg.xdg.configDirectory}/starship/config.toml"; + + programs.fish.config = lib.mkIf cfg.enableFishIntegration ( + lib.mkAfter '' + if test "$TERM" != "dumb" + starship init fish | source + ${lib.optionalString cfg.enableTransience "enable_transience"} + end + '' + ); + + file.xdg_config."starship/config.toml".source = + settingsFormat.generate "starship-config.toml" cfg.settings; + }; + +}
diff --git a/maidModules/xdg.nix b/maidModules/xdg.nix @@ -0,0 +1,121 @@ +{ + options, + config, + lib, + pkgs, + ... +}: +let + inherit (lib) types; + listOrSingletonOf = + type: with types; coercedTo (either (listOf type) type) lib.toList (listOf type); + + cfg = config.xdg; + mimeappsFormat = pkgs.formats.ini { listToValue = lib.concatStringsSep ";"; }; + +in +{ + + options.xdg = { + enable = lib.mkEnableOption "xdg foo"; + cacheDirectory = lib.mkOption { + type = types.str; + default = "~/.cache"; + }; + configDirectory = lib.mkOption { + type = types.str; + default = "~/.config"; + }; + + dataDirectory = lib.mkOption { + type = types.str; + default = "~/.local/share"; + }; + + stateDirectory = lib.mkOption { + type = types.str; + default = "~/.local/state"; + }; + + mime-apps = { + addedAssociations = lib.mkOption { + type = with types; attrsOf (listOrSingletonOf str); + default = { }; + }; + + removedAssociations = lib.mkOption { + type = with types; attrsOf (listOrSingletonOf str); + default = { }; + }; + + defaultApplications = lib.mkOption { + type = with types; attrsOf (listOrSingletonOf str); + default = { }; + }; + }; + }; + + config = lib.mkIf cfg.enable { + environment.sessionVariables = { + XDG_CACHE_HOME = lib.mkIf ( + cfg.cacheDirectory != options.xdg.cacheDirectory.default + ) cfg.cacheDirectory; + XDG_CONFIG_HOME = lib.mkIf ( + cfg.configDirectory != options.xdg.configDirectory.default + ) cfg.configDirectory; + XDG_DATA_HOME = lib.mkIf (cfg.dataDirectory != options.xdg.dataDirectory.default) cfg.dataDirectory; + XDG_STATE_HOME = lib.mkIf ( + cfg.stateDirectory != options.xdg.stateDirectory.default + ) cfg.stateDirectory; + }; + + file = { + xdg_config."mimeapps.list" = + let + nonDefault = { + added = cfg.mime-apps.addedAssociations != options.xdg.mime-apps.addedAssociations.default; + removed = cfg.mime-apps.removedAssociations != options.xdg.mime-apps.removedAssociations.default; + default = cfg.mime-apps.defaultApplications != options.xdg.mime-apps.defaultApplications.default; + }; + in + lib.mkIf (lib.lists.any lib.trivial.id (lib.attrValues nonDefault)) { + source = mimeappsFormat.generate "mimeapps.list" { + "Added Associations" = cfg.mime-apps.addedAssociations; + "Removed Associations" = cfg.mime-apps.removedAssociations; + "Default Applications" = cfg.mime-apps.defaultApplications; + }; + }; + xdg_data."glib-2.0/schemas".source = pkgs.buildEnv { + name = "user-gsettings-schemas"; + paths = config.packages; + pathsToLink = [ "/share/gsettings-schemas" ]; + ignoreCollisions = true; + postBuild = '' + if [ -x $out/bin/glib-compile-schemas -a -w $out/share/glib-2.0/schemas ]; then + $out/bin/glib-compile-schemas $out/share/glib-2.0/schemas + fi + + if [ -d $out/share/gsettings-schemas/ ]; then + # symlink any schema files to the standard schema directory + for d in $out/share/gsettings-schemas/*; do + # Force symlink, in case there are duplicates + ln -fs $d/glib-2.0/schemas/*.xml $out + done + + # and compile them + if [ -w $out ]; then + ${pkgs.glib.dev}/bin/glib-compile-schemas $out + fi + fi + ''; + }; + + # pkgs.symlinkJoin { + # name = "user-gsettings-schemas"; + # paths = config.packages; + # stripPrefix = "/share/gsettings-schemas"; + # }; + }; + }; + +}
diff --git a/nixosModules/common/configure/boot.nix b/nixosModules/common/configure/boot.nix @@ -0,0 +1,91 @@ +{ + npins, + lib, + config, + pkgs, + ... +}: +let + inherit (lib) types; + cfg = config.common.configure.boot; + lanzaboote = import npins.lanzaboote { + inherit pkgs; + }; + +in +{ + + options.common.configure.boot = { + enable = lib.mkEnableOption "basic systemd-boot config"; + secureboot = lib.mkEnableOption "secureboot support via lanzaboote"; + + configurationLimit = lib.mkOption { + type = types.number; + default = 10; + }; + plymouth = { + enable = lib.mkEnableOption "graphical bootscreen"; + theme = lib.mkOption { + type = types.str; + default = "bgrt"; + }; + }; + }; + + imports = + (lib.mkIf cfg.enable [ + lanzaboote.nixosModules.lanzaboote + ]).content; + + config = lib.mkIf cfg.enable ( + lib.mkMerge [ + { + boot.initrd.systemd.enable = true; + boot.loader = { + grub.enable = false; + systemd-boot = { + enable = lib.mkDefault true; + inherit (cfg) configurationLimit; + }; + efi = { + canTouchEfiVariables = true; + efiSysMountPoint = "/boot"; + }; + }; + } + + (lib.mkIf cfg.plymouth.enable { + boot = { + consoleLogLevel = 0; + initrd.verbose = false; + kernelParams = [ + "quiet" + "udev.log_level=3" + ]; + plymouth = { + enable = true; + inherit (cfg) theme; + }; + }; + }) + + (lib.mkIf cfg.secureboot { + environment.systemPackages = with pkgs; [ sbctl ]; + + common.configure.persist.system.dirs = [ + "/var/lib/sbctl" + ]; + + boot = { + loader.systemd-boot.enable = lib.mkForce false; + lanzaboote = { + enable = true; + pkiBundle = "/var/lib/sbctl"; + inherit (cfg) configurationLimit; + }; + }; + }) + ] + ); + +}
diff --git a/nixosModules/common/configure/dn42Router.nix b/nixosModules/common/configure/dn42Router.nix @@ -0,0 +1,315 @@ +{ + lib, + config, + pkgs, + ... +}: + +let + inherit (lib) types; + cfg = config.common.configure.dn42Router; + +in +{ + + options.common.configure.dn42Router = { + enable = lib.mkEnableOption ""; + asn = lib.mkOption { + type = types.int; + }; + routerId = lib.mkOption { + type = types.int; + }; + address = lib.mkOption { + type = types.str; + }; + range = lib.mkOption { + type = types.str; + }; + peerings = lib.mkOption { + default = { }; + type = + with types; + attrsOf (submodule { + options = { + asn = lib.mkOption { type = types.int; }; + remoteLinkLocalAddress = lib.mkOption { type = types.str; }; + localLinkLocalAddress = lib.mkOption { + type = types.str; + default = "fe80::6b61/64"; + }; + endpoint = lib.mkOption { + type = with types; nullOr str; + default = null; + }; + listenPort = lib.mkOption { type = types.int; }; + publicKey = lib.mkOption { type = types.str; }; + hasPresharedKey = lib.mkOption { + type = types.bool; + default = false; + }; + }; + }); + }; + babel.enable = lib.mkOption { + type = types.bool; + default = false; + }; + babel.peerings = lib.mkOption { + default = { }; + type = + with types; + attrsOf (submodule { + options = { + type = lib.mkOption { + type = types.str; + default = "tunnel"; + }; + }; + }); + }; + }; + + config = lib.mkIf cfg.enable { + networking.firewall.allowedUDPPorts = lib.mapAttrsToList ( + _name: peerConfig: peerConfig.listenPort + ) cfg.peerings; + + sops.secrets = lib.pipe cfg.peerings [ + (lib.mapAttrsToList ( + name: peerConfig: + [ + (lib.nameValuePair "dn42/peerings/${name}/wgPrivateKey" { + owner = "systemd-network"; + group = "systemd-network"; + }) + ] + ++ lib.optionals peerConfig.hasPresharedKey [ + (lib.nameValuePair "dn42/peerings/${name}/wgPresharedKey" { + owner = "systemd-network"; + group = "systemd-network"; + }) + ] + )) + lib.lists.flatten + lib.listToAttrs + ]; + + systemd = { + network = { + netdevs = lib.mapAttrs' ( + name: peerConfig: + lib.nameValuePair "20-dn42${name}" { + netdevConfig = { + Kind = "wireguard"; + Name = "dn42${name}"; + }; + wireguardConfig = { + ListenPort = peerConfig.listenPort; + PrivateKeyFile = config.sops.secrets."dn42/peerings/${name}/wgPrivateKey".path; + } + // (lib.optionalAttrs peerConfig.hasPresharedKey { + PresharedKeyFile = config.sops.secrets."dn42/peerings/${name}/wgPresharedKey".path; + }); + wireguardPeers = [ + { + PersistentKeepalive = 30; + Endpoint = lib.mkIf (!builtins.isNull peerConfig.endpoint) peerConfig.endpoint; + PublicKey = peerConfig.publicKey; + AllowedIPs = [ + "fd00::/8" + peerConfig.remoteLinkLocalAddress + ]; + } + ]; + } + ) cfg.peerings; + + networks = lib.mapAttrs' ( + name: peerConfig: + lib.nameValuePair "20-dn42${name}" { + matchConfig.Name = "dn42${name}"; + linkConfig.RequiredForOnline = "no"; + + address = [ peerConfig.localLinkLocalAddress ]; + routes = [ { Destination = "${peerConfig.remoteLinkLocalAddress}/128"; } ]; + + networkConfig = { + IPv6Forwarding = true; + IPv6AcceptRA = false; + DHCP = false; + }; + } + ) cfg.peerings; + }; + services = { + systemd-networkd.after = [ "sops-install-secrets.service" ]; + stayrtr = { + wantedBy = [ + "multi-user.target" + "bird.service" + ]; + serviceConfig.DynamicUser = true; + serviceConfig.ExecStart = '' + ${lib.getExe pkgs.stayrtr} \ + -bind [::1]:8282 \ + -cache=https://dn42.burble.com/roa/dn42_roa_46.json \ + -checktime=false + ''; + }; + }; + }; + + services.bird = { + enable = true; + package = pkgs.bird3; + preCheckConfig = '' + # Remove roa files for checking, because they are only available at runtime + sed -i 's|include "/etc/bird/roa_dn42.conf";||' bird.conf + + cat -n bird.conf # here for debugging purposes + ''; + config = '' + log syslog { debug, trace, info, remote, warning, error, auth, fatal, bug }; + log stderr all; + + define OWNAS = ${toString cfg.asn}; + define OWNNET = ${cfg.range}; + define OWNNETSET = [ ${cfg.range} ]; + define OWNIP = ${cfg.address}; + + router id ${toString cfg.routerId}; + hostname "${config.networking.hostName}"; + + roa6 table dn42_roa; + + function is_self_net() -> bool { + return net ~ OWNNETSET; + } + + function is_valid_network() -> bool { + return net ~ [ + fd00::/8{44,64} + ]; + } + + function import_filter() { + if (net.type != NET_IP6 || ! is_valid_network() || is_self_net()) then + reject; + + if (roa_check(dn42_roa, net, bgp_path.last) != ROA_VALID) then { + # Reject when unknown or invalid according to ROA + print "[dn42] ROA check failed for ", net, " ASN ", bgp_path.last; + reject; + } + + accept; + } + + function export_filter() { + if ( ! is_valid_network() ) then + reject; + + if (source !~ [RTS_STATIC, RTS_BGP]) then + reject; + + accept; + } + + protocol rpki { + roa6 { table dn42_roa; }; + remote ::1; + port 8282; + refresh 600; + retry 300; + expire 7200; + } + + protocol device { + scan time 10; + } + + protocol static { + route OWNNET unreachable; + + ipv6 { + import all; + export none; + }; + } + + protocol kernel { + scan time 20; + + ipv6 { + import none; + export filter { + # dont export static routes + if source = RTS_STATIC then reject; + # preferred outgoing source address + krt_prefsrc = OWNIP; + accept; + }; + }; + } + + template bgp dn42_peers { + local as OWNAS; + path metric 1; + advertise hostname on; + enforce first as on; + med metric on; + + ipv6 { + import keep filtered; + import limit 9000 action block; + import where import_filter(); + next hop self; # advertise this router as next hop + + export where export_filter(); + }; + } + '' + + (lib.concatStringsSep "\n" ( + lib.mapAttrsToList (name: peerConfig: '' + protocol bgp ${name} from dn42_peers { + neighbor ${peerConfig.remoteLinkLocalAddress}%dn42${name} as ${toString peerConfig.asn}; + enable extended messages; + } + '') cfg.peerings + )) + + (lib.optionalString cfg.babel.enable '' + /* babel config */ + protocol direct { + ipv6; + + ${lib.concatStringsSep "\n" ( + lib.mapAttrsToList (name: _peerConfig: '' + interface "${name}"; + '') cfg.babel.peerings + )} + }; + + protocol babel { + randomize router id on; + + ipv6 { + import where source != RTS_BGP && is_self_net(); + export where source != RTS_BGP && is_self_net(); + }; + + ${lib.concatStringsSep "\n" ( + lib.mapAttrsToList (name: peerConfig: '' + interface "${name}" { + type ${peerConfig.type}; + next hop prefer ipv6; + }; + '') cfg.babel.peerings + )} + }; + /* End of babel config */ + ''); + }; + }; + +}
diff --git a/nixosModules/common/configure/fonts.nix b/nixosModules/common/configure/fonts.nix @@ -0,0 +1,21 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.common.configure.fonts.enable = lib.mkEnableOption "basic font config"; + + config = lib.mkIf config.common.configure.fonts.enable { + fonts = { + fontconfig.enable = true; + fontDir.enable = true; + enableGhostscriptFonts = true; + enableDefaultPackages = true; + }; + }; + +}
diff --git a/nixosModules/common/configure/locale.nix b/nixosModules/common/configure/locale.nix @@ -0,0 +1,34 @@ +{ + config, + lib, + ... +}: + +{ + + options.common.configure.locale.enable = lib.mkEnableOption "locale configuration"; + + config = lib.mkIf config.common.configure.locale.enable { + time.timeZone = "Europe/Berlin"; + i18n = { + defaultLocale = "en_GB.UTF-8"; + supportedLocales = [ + "en_GB.UTF-8/UTF-8" + "de_DE.UTF-8/UTF-8" + ]; + + extraLocaleSettings = { + LC_ADDRESS = "de_DE.UTF-8"; + LC_IDENTIFICATION = "de_DE.UTF-8"; + LC_MEASUREMENT = "de_DE.UTF-8"; + LC_MONETARY = "de_DE.UTF-8"; + LC_NAME = "de_DE.UTF-8"; + LC_NUMERIC = "en_GB.UTF-8"; + LC_PAPER = "de_DE.UTF-8"; + LC_TELEPHONE = "de_DE.UTF-8"; + LC_TIME = "en_GB.UTF-8"; + }; + }; + }; + +}
diff --git a/nixosModules/common/configure/nix.nix b/nixosModules/common/configure/nix.nix @@ -0,0 +1,59 @@ +{ + npins, + config, + lib, + pkgs, + ... +}: + +{ + + options.common.configure.nix.enable = lib.mkEnableOption "sane nix defaults"; + + imports = + (lib.mkIf config.common.nix.enable [ + (import "${npins.lixModule}/module.nix" { lix = null; }) + ]).content; + + config = lib.mkIf config.common.configure.nix.enable ( + let + NIXPKGS_PATH = lib.cleanSource npins.nixpkgs; + NIXPKGS_UNSTABLE_PATH = lib.cleanSource npins.nixpkgsUnstable; + + in + { + nix = { + package = pkgs.lix; + nixPath = lib.mkForce [ "nixpkgs=${NIXPKGS_PATH}" ]; + channel.enable = lib.mkForce false; + + registry = { + nixpkgs.to = { + type = "path"; + path = NIXPKGS_PATH; + }; + + nixpkgsUnstable.to = { + type = "path"; + path = NIXPKGS_UNSTABLE_PATH; + }; + }; + + settings = { + nix-path = config.nix.nixPath; + trusted-users = [ "@wheel" ]; + use-xdg-base-directories = true; + experimental-features = [ + "flakes" + "nix-command" + ]; + }; + }; + + environment.sessionVariables = { + inherit NIXPKGS_PATH NIXPKGS_UNSTABLE_PATH; + }; + } + ); + +}
diff --git a/nixosModules/common/configure/persist.nix b/nixosModules/common/configure/persist.nix @@ -0,0 +1,167 @@ +{ + npins, + config, + lib, + pkgs, + ... +}: +let + inherit (lib) types; + + cfg = config.common.configure.persist; + + perms = { + user = lib.mkOption { + type = types.str; + default = "root"; + }; + group = lib.mkOption { + type = types.str; + default = "root"; + }; + mode = lib.mkOption { + type = with types; nullOr str; + default = null; + }; + }; + +in +{ + + options.common.configure.persist = { + home.enable = lib.mkOption { + type = types.bool; + default = false; + }; + system = { + enable = lib.mkOption { + type = types.bool; + default = false; + }; + dirs = lib.mkOption { + default = [ ]; + type = + with types; + listOf (oneOf [ + str + (submodule { + options = { + directory = lib.mkOption { type = types.str; }; + } + // perms; + }) + ]); + }; + files = lib.mkOption { + default = [ ]; + type = + with types; + listOf (oneOf [ + str + (submodule { + options = { + file = lib.mkOption { type = types.str; }; + parentDirectory = lib.mkOption { + type = with types; nullOr (submodule perms); + default = null; + }; + } + // perms; + }) + ]); + }; + + }; + }; + + imports = + (lib.mkIf cfg.enable [ + "${npins.preservation}/module.nix" + ]).content; + + config = lib.mkMerge [ + (lib.mkIf cfg.home.enable { + common.configure.rootDisk.subVolumes.home = true; + programs.fuse.userAllowOther = lib.mkDefault true; + preservation.enable = true; + preservation.preserveAt."/persist" = { }; + }) + (lib.mkIf cfg.system.enable { + common.configure.rootDisk.subVolumes.system = true; + + boot.initrd.systemd.services.defenestrate = { + description = "Defenestrate old btrfs-root"; + wantedBy = [ "initrd.target" ]; + after = [ "cryptsetup.target" ]; + before = [ "sysroot.mount" ]; + onFailure = [ "emergency.target" ]; + unitConfig.DefaultDependencies = "no"; + serviceConfig.Type = "oneshot"; + script = '' + mkdir -p /btrfs_tmp + mount -o "subvol=/" /dev/mapper/root /btrfs_tmp + + ${lib.getExe pkgs.btrfs-progs} subvolume delete --recursive /btrfs_tmp/nixos-root-4 + + mv /btrfs_tmp/nixos-root-3 /btrfs_tmp/nixos-root-4 + mv /btrfs_tmp/nixos-root-2 /btrfs_tmp/nixos-root-3 + mv /btrfs_tmp/nixos-root-1 /btrfs_tmp/nixos-root-2 + + ${lib.getExe pkgs.btrfs-progs} subvolume create /btrfs_tmp/nixos-root-1 + ${lib.getExe pkgs.btrfs-progs} subvolume set-default /btrfs_tmp/nixos-root-1 + + umount /btrfs_tmp + ''; + }; + + systemd.tmpfiles.rules = [ + "d /var/lib/private 0700 root root" + ]; + systemd.services = lib.genAttrs [ "sops-install-secrets" "sops-install-secrets-for-users" ] (_: { + unitConfig.RequiresMountsFor = [ "/persist/system" ]; + }); + + systemd.tmpfiles.settings.preservation = + let + directories = map ( + elem: if (lib.isAttrs elem) then elem.directory else elem + ) config.preservation.preserveAt."/persist/system".directories; + in + lib.genAttrs (lib.filter (elem: lib.hasPrefix "/var/lib/private/" elem) directories) (name: { + "d" = { + type = "!d"; + mode = lib.mkForce "0700"; + }; + }); + + preservation.enable = true; + preservation.preserveAt."/persist/system" = { + directories = [ + "/var/log" + "/var/lib/systemd/coredump" + "/var/lib/systemd/rfkill" + "/var/lib/systemd/timers" + { + directory = "/var/lib/nixos"; + inInitrd = true; + } + # { + # directory = "/var/lib/private"; + # mode = "0700"; + # } + ] + ++ cfg.system.dirs; + files = [ + { + file = "/var/lib/systemd/random-seed"; + how = "symlink"; + inInitrd = true; + configureParent = true; + } + ] + ++ cfg.system.files; + }; + }) + ]; + +}
diff --git a/nixosModules/common/configure/primaryNetworkInterface.nix b/nixosModules/common/configure/primaryNetworkInterface.nix @@ -0,0 +1,94 @@ +{ + machineConfig, + config, + lib, + ... +}: +let + inherit (lib) types; + cfg = config.common.configure.primaryNetworkInterface; + +in +{ + + options.common.configure.primaryNetworkInterface = { + enable = lib.mkEnableOption "configuration of the primary network interface"; + interfaceName = lib.mkOption { + type = types.str; + default = machineConfig.networking.primaryInterface; + }; + dnsServers = lib.mkOption { + type = with types; listOf str; + default = [ + "1.1.1.1" + "8.8.8.8" + "9.9.9.9" + ]; + }; + useDHCP = lib.mkOption { + type = types.bool; + default = if (cfg.ip4Address != null) then false else true; + }; + acceptRouterAdvertisements = lib.mkOption { + type = types.bool; + default = if (cfg.ip6Address != null) then false else true; + }; + ip4Address = lib.mkOption { + type = with types; nullOr str; + default = + if (machineConfig.networking ? ip4Address && machineConfig.networking ? ip4PrefixLength) then + "${machineConfig.networking.ip4Address}/${toString machineConfig.networking.ip4PrefixLength}" + else + null; + }; + ip4DefaultGateway = lib.mkOption { + type = with types; nullOr str; + default = + if machineConfig.networking ? defaultGateway4 then + machineConfig.networking.defaultGateway4 + else + null; + }; + ip6Address = lib.mkOption { + type = with types; nullOr str; + default = + if (machineConfig.networking ? ip6Address && machineConfig.networking ? ip6PrefixLength) then + "${machineConfig.networking.ip6Address}/${toString machineConfig.networking.ip6PrefixLength}" + else + null; + }; + ip6DefaultGateway = lib.mkOption { + type = with types; nullOr str; + default = + if machineConfig.networking ? defaultGateway6 then + machineConfig.networking.defaultGateway6 + else + null; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.network = { + networks = { + "5-primaryInterface" = { + enable = true; + name = cfg.interfaceName; + networkConfig = { + DHCP = cfg.useDHCP; + IPv6AcceptRA = cfg.acceptRouterAdvertisements; + }; + dns = cfg.dnsServers; + gateway = [ + (lib.mkIf (cfg.ip4DefaultGateway != null) cfg.ip4DefaultGateway) + (lib.mkIf (cfg.ip6DefaultGateway != null) cfg.ip6DefaultGateway) + ]; + address = [ + (lib.mkIf (cfg.ip4Address != null) cfg.ip4Address) + (lib.mkIf (cfg.ip6Address != null) cfg.ip6Address) + ]; + }; + }; + }; + }; + +}
diff --git a/nixosModules/common/configure/rootDisk.nix b/nixosModules/common/configure/rootDisk.nix @@ -0,0 +1,185 @@ +{ + config, + lib, + pkgs, + ... +}: +let + inherit (lib) types; + cfg = config.common.configure.rootDisk; + users = lib.pipe config.common.users [ + (lib.mapAttrsToList (name: value: if value.enable then name else null)) + (lib.filter (element: !builtins.isNull element)) + ]; + partOpts = [ + "compress=zstd" + "discard=async" + ]; + +in +{ + + options.common.configure.rootDisk = { + enable = lib.mkOption { + type = types.bool; + default = false; + }; + subVolumes = { + nix = lib.mkOption { + type = types.bool; + default = true; + }; + home = lib.mkOption { + type = types.bool; + default = false; + }; + homePerUser = lib.mkOption { + type = types.bool; + default = cfg.subVolumes.home; + }; + tmp = lib.mkOption { + type = types.bool; + default = false; + }; + system = lib.mkOption { + type = types.bool; + default = false; + }; + }; + swap = { + enable = lib.mkOption { + type = types.bool; + default = false; + }; + size = lib.mkOption { + type = types.strMatching "[0-9]+[KMGTP]"; + }; + }; + }; + + config = lib.mkIf cfg.enable ( + let + askPass = pkgs.writeShellScriptBin "cryptsetup-askpass" "systemctl default"; + in + { + boot = { + supportedFilesystems = [ "btrfs" ]; + initrd = { + luks.devices."root".device = "/dev/disk/by-partlabel/${config.networking.hostName}-root"; + + availableKernelModules = [ + "btrfs" + "ipv6" + ]; + + systemd = { + enable = lib.mkDefault true; + network = lib.mkIf config.systemd.network.enable config.systemd.network; + storePaths = [ "${askPass}/bin/cryptsetup-askpass" ]; + users.root.shell = "${askPass}/bin/cryptsetup-askpass"; + }; + + network = { + enable = lib.mkDefault true; + ssh = { + enable = lib.mkDefault true; + port = 22; + hostKeys = lib.mkDefault (lib.map (element: element.path) config.services.openssh.hostKeys); + authorizedKeys = + with lib; + concatLists ( + mapAttrsToList ( + name: user: if elem "wheel" user.extraGroups then user.openssh.authorizedKeys.keys else [ ] + ) config.users.users + ); + }; + }; + }; + }; + + services.btrfs.autoScrub.enable = true; + + swapDevices = lib.singleton { device = lib.mkIf cfg.swap.enable "/swap/swapfile"; }; + + fileSystems = lib.mkMerge [ + { + "/" = { + device = "/dev/mapper/root"; + fsType = "btrfs"; + options = partOpts; + }; + "/boot" = { + device = "/dev/disk/by-partlabel/${config.networking.hostName}-boot"; + fsType = "vfat"; + options = [ + "nofail" + "umask=0077" + "dmask=0077" + ]; + }; + "/tmp" = lib.mkIf cfg.subVolumes.tmp { + device = "/dev/mapper/root"; + fsType = "btrfs"; + options = [ + "subvol=nix" + "nosuid" + "nodev" + ]; + }; + "/nix" = lib.mkIf cfg.subVolumes.nix { + device = "/dev/mapper/root"; + fsType = "btrfs"; + options = [ + "subvol=nix" + "noatime" + ] + ++ partOpts; + }; + "/persist/system" = lib.mkIf cfg.subVolumes.system { + device = "/dev/mapper/root"; + fsType = "btrfs"; + options = [ + "subvol=system" + "noatime" + ] + ++ partOpts; + }; + "/persist/home" = lib.mkIf (cfg.subVolumes.home && !cfg.subVolumes.homePerUser) { + device = "/dev/mapper/root"; + fsType = "btrfs"; + options = [ + "subvol=home" + ] + ++ partOpts; + }; + "/swap" = lib.mkIf cfg.swap.enable { + device = "/dev/mapper/root"; + fsType = "btrfs"; + options = [ + "subvol=swap" + "noatime" + "discard=async" + ]; + }; + } + (lib.mkIf cfg.subVolumes.homePerUser ( + lib.listToAttrs ( + lib.map (user: { + name = + if config.common.configure.persist.home.enable then "/persist/home/${user}" else "/home/${user}"; + value = { + device = "/dev/mapper/root"; + fsType = "btrfs"; + options = [ + "subvol=home-${user}" + ] + ++ partOpts; + }; + }) users + ) + )) + ]; + } + ); + +}
diff --git a/nixosModules/common/configure/sops.nix b/nixosModules/common/configure/sops.nix @@ -0,0 +1,22 @@ +{ + sopsSecrets, + npins, + config, + lib, + ... +}: + +{ + + options.common.configure.sops.enable = lib.mkEnableOption "sops secrets"; + + imports = + (lib.mkIf config.common.configure.sops.enable [ + "${npins.sopsNix}/modules/sops" + ]).content; + + config = lib.mkIf config.common.configure.sops.enable { + sops.defaultSopsFile = sopsSecrets.${config.networking.hostName}; + }; + +}
diff --git a/nixosModules/common/hardware/audio.nix b/nixosModules/common/hardware/audio.nix @@ -0,0 +1,21 @@ +{ + lib, + config, + ... +}: +{ + + options.common.hardware.audio.enable = lib.mkEnableOption "basic pipewire config"; + + config = lib.mkIf config.common.hardware.audio.enable { + security.rtkit.enable = true; + + services.pipewire = { + enable = true; + alsa.enable = true; + pulse.enable = true; + wireplumber.enable = true; + }; + }; + +}
diff --git a/nixosModules/common/hardware/bluetooth.nix b/nixosModules/common/hardware/bluetooth.nix @@ -0,0 +1,22 @@ +{ + config, + lib, + ... +}: +{ + + options.common.hardware.bluetooth.enable = lib.mkEnableOption "bluetooth foo"; + + config = lib.mkIf config.common.hardware.bluetooth.enable { + common.configure.persist.system.dirs = [ + "/var/lib/bluetooth" + ]; + + hardware.bluetooth = { + enable = true; + powerOnBoot = lib.mkDefault false; + settings.General.Experimental = lib.mkDefault true; + }; + }; + +}
diff --git a/nixosModules/common/hardware/fprint.nix b/nixosModules/common/hardware/fprint.nix @@ -0,0 +1,23 @@ +{ + config, + lib, + ... +}: +let + inherit (lib) types; + cfg = config.common.hardware.fingerprint; + +in +{ + + options.common.hardware.fingerprint.enable = lib.mkEnableOption "fingerprint sensor support"; + + config = lib.mkIf cfg.enable { + services.fprintd.enable = true; + + common.configure.persist.system.dirs = [ + "/var/lib/fprint" + ]; + }; + +}
diff --git a/nixosModules/common/hardware/intelGraphics.nix b/nixosModules/common/hardware/intelGraphics.nix @@ -0,0 +1,24 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.common.hardware.intelGraphics.enable = lib.mkEnableOption "intel gpu drivers"; + + config = lib.mkIf config.common.hardware.intelGraphics.enable { + boot.initrd.kernelModules = [ "i915" ]; + + hardware.graphics = { + enable = true; + extraPackages = with pkgs; [ + vpl-gpu-rt + intel-media-driver + ]; + }; + }; + +}
diff --git a/nixosModules/common/hardware/quirks.nix b/nixosModules/common/hardware/quirks.nix @@ -0,0 +1,23 @@ +{ + config, + lib, + ... +}: +let + cfg = config.common.hardware.quirks; + +in +{ + + options.common.hardware.quirks = { + thinkpad.enable = lib.mkEnableOption "thinkpad quirks"; + }; + + config = lib.mkMerge [ + (lib.mkIf cfg.thinkpad.enable { + boot.extraModprobeConfig = '' + options thinkpad_acpi fan_control=1 + ''; + }) + ]; +}
diff --git a/nixosModules/common/hardware/smartcard.nix b/nixosModules/common/hardware/smartcard.nix @@ -0,0 +1,19 @@ +{ + config, + lib, + pkgs, + ... +}: +{ + + options.common.hardware.smartcard.enable = lib.mkEnableOption "smartcard spport"; + + config = lib.mkIf config.common.hardware.smartcard.enable { + services = { + pcscd.enable = true; + udev.packages = with pkgs; [ libu2f-host ]; + dbus.packages = with pkgs; [ gcr ]; + }; + }; + +}
diff --git a/nixosModules/common/hardware/thunderbolt.nix b/nixosModules/common/hardware/thunderbolt.nix @@ -0,0 +1,15 @@ +{ + config, + lib, + ... +}: +{ + + options.common.hardware.thunderbolt.enable = lib.mkEnableOption "thundebolt support"; + + config = lib.mkIf config.common.hardware.thunderbolt.enable { + boot.initrd.availableKernelModules = [ "thunderbolt" ]; + services.hardware.bolt.enable = true; + }; + +}
diff --git a/nixosModules/common/profiles/amdCpu.nix b/nixosModules/common/profiles/amdCpu.nix @@ -0,0 +1,23 @@ +{ + config, + lib, + ... +}: + +{ + + options.common.profiles.amdCpu.enable = lib.mkEnableOption "amd-cpu profile"; + + config = lib.mkIf config.common.profiles.amdCpu.enable { + hardware.cpu.amd.updateMicrocode = true; + + boot = { + kernelParams = [ "amd_pstate=active" ]; + kernelModules = [ + "kvm-amd" + "amd-pstate" + ]; + }; + }; + +}
diff --git a/nixosModules/common/profiles/base.nix b/nixosModules/common/profiles/base.nix @@ -0,0 +1,103 @@ +{ + pkgs, + lib, + config, + machineConfig, + ... +}: + +{ + + options.common.profiles.base.enable = lib.mkEnableOption "base profile"; + + config = lib.mkIf config.common.profiles.base.enable { + boot.kernel.sysctl."kernel.sysrq" = lib.mkDefault 1; + + # make things more declerative + services.userborn.enable = lib.mkDefault true; + #users.mutableUsers = lib.mkForce false; + + networking = { + hostId = builtins.substring 0 8 (builtins.hashString "sha256" machineConfig.machineName); + hostName = machineConfig.machineName; + domain = lib.mkDefault machineConfig.domain; + + useNetworkd = lib.mkDefault true; + useDHCP = lib.mkDefault false; + + nftables.enable = lib.mkDefault true; + firewall.enable = lib.mkDefault true; + }; + + hardware.enableRedistributableFirmware = true; + + common = { + profiles = { + amdCpu.enable = lib.mkDefault (machineConfig.hardware.cpuVendor == "amd"); + intelCpu.enable = lib.mkDefault (machineConfig.hardware.cpuVendor == "intel"); + }; + + configure = { + boot.enable = lib.mkDefault true; + + locale.enable = lib.mkDefault true; + sops.enable = lib.mkDefault true; + + nix.enable = true; + }; + + services = { + openssh.enable = true; + }; + + security = { + nix.enable = lib.mkDefault true; + kernel.enable = lib.mkDefault true; + networking.enable = lib.mkDefault true; + }; + + programs = { + shellUtilities.enable = lib.mkDefault true; + systemUtilities.enable = lib.mkDefault true; + networkUtilities.enable = lib.mkDefault true; + + fish.enable = lib.mkDefault true; + }; + }; + + programs = { + command-not-found.enable = false; # Not usable without channels; use nix-index instead. + }; + + services = { + dbus.implementation = "broker"; + }; + + security.sudo.extraConfig = "Defaults lecture=\"never\""; # "We trust you have received the usual lecture from the local System Administrator." + + system = { + stateVersion = lib.mkDefault "25.11"; + + # thanks piegames (https://git.darmstadt.ccc.de/piegames/home-config/-/blob/master/modules/generic.nix#L84) + activationScripts = { + diff = { + supportsDryActivation = true; + text = '' + ${pkgs.nvd}/bin/nvd --color=always --nix-bin-dir=${pkgs.nix}/bin diff "$(readlink /run/current-system)" "$systemConfig" + # Ignore "failures" because these tools have weird exit codes + ${pkgs.colordiff}/bin/colordiff --nobanner --fakeexitcode --color=always -ur -I '\/nix\/store' \ + -- "$(readlink /run/current-system)/activate" "$systemConfig/activate" | ${pkgs.gnugrep}/bin/grep -v "^Binary files" || true + ${pkgs.colordiff}/bin/colordiff --nobanner --fakeexitcode --color=always -ur -I '\/nix\/store' \ + -x "os-release" -x "issue" \ + -- "$(readlink /run/current-system)/etc" "$systemConfig/etc" | ${pkgs.gnugrep}/bin/grep -v "^Binary files" || true + ${pkgs.colordiff}/bin/colordiff --nobanner --fakeexitcode --color=always -ur -I '\/nix\/store' \ + -x "environment.d" \ + -x "hwdb.d" \ + -- "$(readlink /run/current-system)/systemd" "$systemConfig/systemd" | ${pkgs.gnugrep}/bin/grep -v "^Binary files" || true + ''; + }; + }; + }; + }; + +}
diff --git a/nixosModules/common/profiles/intelCpu.nix b/nixosModules/common/profiles/intelCpu.nix @@ -0,0 +1,17 @@ +{ + config, + lib, + ... +}: + +{ + + options.common.profiles.intelCpu.enable = lib.mkEnableOption "intel-cpu profile"; + + config = lib.mkIf config.common.profiles.intelCpu.enable { + boot.initrd.kernelModules = [ "kvm-intel" ]; + + hardware.cpu.intel.updateMicrocode = true; + }; + +}
diff --git a/nixosModules/common/profiles/minimal.nix b/nixosModules/common/profiles/minimal.nix @@ -0,0 +1,48 @@ +{ + lib, + config, + ... +}: + +{ + + options.common.profiles.minimal.enable = lib.mkEnableOption "minimal system profile"; + + config = lib.mkIf config.common.profiles.minimal.enable { + boot.enableContainers = lib.mkDefault false; + + system.disableInstallerTools = lib.mkDefault true; + + documentation = { + enable = lib.mkDefault false; + doc.enable = lib.mkDefault false; + info.enable = lib.mkDefault false; + man.enable = lib.mkDefault false; + nixos.enable = lib.mkDefault false; + }; + + environment = { + defaultPackages = lib.mkDefault [ ]; + stub-ld.enable = lib.mkDefault false; + }; + + programs = { + less.lessopen = lib.mkDefault null; + command-not-found.enable = lib.mkDefault false; + fish.generateCompletions = lib.mkDefault false; + }; + + services = { + logrotate.enable = lib.mkDefault false; + udisks2.enable = lib.mkDefault false; + }; + + xdg = { + autostart.enable = lib.mkDefault false; + icons.enable = lib.mkDefault false; + mime.enable = lib.mkDefault false; + sounds.enable = lib.mkDefault false; + }; + }; + +}
diff --git a/nixosModules/common/profiles/netcup.nix b/nixosModules/common/profiles/netcup.nix @@ -0,0 +1,34 @@ +{ + config, + lib, + ... +}: + +{ + + options.common.profiles.netcup.enable = lib.mkEnableOption "netcup vserver profile"; + + config = lib.mkIf config.common.profiles.netcup.enable { + boot = { + initrd.availableKernelModules = [ + "ata_piix" + "uhci_hcd" + "virtio_pci" + "sr_mod" + "virtio_blk" + ]; + }; + + common.configure.primaryNetworkInterface = { + interfaceName = "ens3"; + ip6DefaultGateway = "fe80::1"; + dnsServers = [ + "46.38.225.230" + "46.38.252.230" + "2a03:4000:0:1::e1e6" + "2a03:4000:8000::fce6" + ]; + }; + }; + +}
diff --git a/nixosModules/common/profiles/nvme.nix b/nixosModules/common/profiles/nvme.nix @@ -0,0 +1,18 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.common.profiles.nvme.enable = lib.mkEnableOption "nvme profile"; + + config = lib.mkIf config.common.profiles.nvme.enable { + boot.initrd.availableKernelModules = [ "nvme" ]; + + environment.systemPackages = [ pkgs.nvme-cli ]; + }; + +}
diff --git a/nixosModules/common/programs/fish.nix b/nixosModules/common/programs/fish.nix @@ -0,0 +1,17 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.common.programs.fish.enable = lib.mkEnableOption "fish shell"; + + config = lib.mkIf config.common.programs.fish.enable { + programs.fish.enable = true; + users.defaultUserShell = pkgs.fish; + }; + +}
diff --git a/nixosModules/common/programs/networkUtilities.nix b/nixosModules/common/programs/networkUtilities.nix @@ -0,0 +1,25 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.common.programs.networkUtilities.enable = lib.mkEnableOption "useful network-utilities"; + + config = lib.mkIf config.common.programs.networkUtilities.enable { + environment.systemPackages = with pkgs; [ + dig + nmap + openssh + whois + ]; + + programs = { + mtr.enable = true; + }; + }; + +}
diff --git a/nixosModules/common/programs/niri.nix b/nixosModules/common/programs/niri.nix @@ -0,0 +1,56 @@ +{ + config, + lib, + pkgs, + ... +}: +{ + + options.common.programs.niri.enable = lib.mkEnableOption "niri"; + config = lib.mkIf config.common.programs.niri.enable { + programs.niri.enable = true; + + systemd.user.services."niri" = { + # Niri ships with a `niri.service` unit, so the configuration here is + # actually a drop-in override. Environment variables here should be forced + # to be `{ }`, otherwise a default `PATH` for services is injected here, + # clearing all inherited paths and making the WM totally unuseable without + # `/run/current-system/sw/bin`, `q/etc/profiles/per-user/<username>/bin` and + # potentially other paths. + environment = lib.mkForce { }; + bindsTo = [ + "graphical-session.target" + "niri-session.target" + ]; + before = [ + "graphical-session.target" + "niri-session.target" + ]; + }; + + systemd.user.targets."niri-session" = { + description = "Current Niri graphical user session"; + documentation = [ "man:systemd.special(7)" ]; + requires = [ "basic.target" ]; + unitConfig.RefuseManualStart = true; + unitConfig.StopWhenUnneeded = true; + }; + + # `maid-activation.service` finishes before `graphical-session-pre.target`, + # so services dependent on a graphical environment won't run without another + # activation. + # See <https://github.com/viperML/nix-maid/issues/53>. + systemd.user.services."maid-graphical-session-activation" = { + wantedBy = [ "graphical-session.target" ]; + after = [ "graphical-session.target" ]; + serviceConfig.Type = "oneshot"; + serviceConfig.ExecStart = "${lib.getExe' pkgs.systemd "systemctl"} --user restart maid-activation.service"; + serviceConfig.RemainAfterExit = true; + }; + + # `nixos-fake-graphical-session.target` breaks some default systemd targets' + # semantics, such as `graphical-session.target`, when using most WMs. + # See <https://github.com/viperML/nix-maid/issues/56> + systemd.user.targets."nixos-fake-graphical-session".enable = false; + }; +}
diff --git a/nixosModules/common/programs/nixUtilities.nix b/nixosModules/common/programs/nixUtilities.nix @@ -0,0 +1,23 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.common.programs.nixUtilities.enable = lib.mkEnableOption "useful nix utilities"; + + config = lib.mkIf config.common.programs.nixUtilities.enable { + environment.systemPackages = with pkgs; [ + deadnix + nixfmt-rfc-style + nixfmt-tree + nixd + statix + nh + ]; + }; + +}
diff --git a/nixosModules/common/programs/shellUtilities.nix b/nixosModules/common/programs/shellUtilities.nix @@ -0,0 +1,37 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.common.programs.shellUtilities.enable = lib.mkEnableOption "useful shell-utilities"; + + config = lib.mkIf config.common.programs.shellUtilities.enable { + environment = { + systemPackages = with pkgs; [ + eza + micro + file + nmap + nvd + openssh + openssl + p7zip + unzip + progress + sops + rsync + zpha.nix-cleanup + ]; + }; + + programs = { + bat.enable = true; + nano.enable = true; + }; + }; + +}
diff --git a/nixosModules/common/programs/systemUtilities.nix b/nixosModules/common/programs/systemUtilities.nix @@ -0,0 +1,24 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.common.programs.systemUtilities.enable = lib.mkEnableOption "system utilities"; + + config = lib.mkIf config.common.programs.systemUtilities.enable { + environment.systemPackages = with pkgs; [ + fastfetch + pciutils + usbutils + ]; + + programs = { + htop.enable = true; + }; + }; + +}
diff --git a/nixosModules/common/security/kernel.nix b/nixosModules/common/security/kernel.nix @@ -0,0 +1,77 @@ +{ + machineConfig, + config, + lib, + ... +}: +{ + + options.common.security.kernel.enable = lib.mkEnableOption "enhanced kernel security"; + + config = lib.mkIf config.common.security.kernel.enable { + environment = { + # memoryAllocator.provider = mkDefault "scudo"; # Breaks stuff + # variables.SCUDO_OPTIONS = mkDefault "ZeroContents=1"; # Breaks stuff + }; + + boot = { + blacklistedKernelModules = [ + # Obscure network protocols + "ax25" + "netrom" + "rose" + + # Old or rare or insufficiently audited filesystems + "adfs" + "affs" + "bfs" + "befs" + "cramfs" + "efs" + "erofs" + "exofs" + "freevxfs" + "f2fs" + "hfs" + "hpfs" + "jfs" + "minix" + "nilfs2" + "ntfs" + "omfs" + "qnx4" + "qnx6" + "sysv" + "ufs" + ]; + kernel.sysctl = { + "kernel.yama.ptrace_scope" = lib.mkOverride 500 1; + "kernel.kptr_restrict" = lib.mkOverride 500 2; + "net.core.bpf_jit_enable" = lib.mkDefault false; + "kernel.ftrace_enabled" = lib.mkDefault false; + }; + kernelParams = lib.mkMerge [ + [ + # Slab/slub sanity checks, redzoning, and poisoning + "slub_debug=FZP" + + # Overwrite free'd memory + "page_poison=1" + + # Enable page allocator randomization + "page_alloc.shuffle=1" + ] + + # Disable hibernation (allows replacing the running kernel) unless requested + (lib.mkIf (!machineConfig.hardware.allowHibernation) [ "nohibernate" ]) + ]; + }; + + # Disable kernel module loading once the system is fully initialised. + # FIXME: Remove reverse dependencies + security.lockKernelModules = lib.mkDefault false; + # Prevent replacing the running kernel image w/o reboot + boot.kernel.sysctl."kernel.kexec_load_disabled" = lib.mkDefault true; + }; + +}
diff --git a/nixosModules/common/security/networking.nix b/nixosModules/common/security/networking.nix @@ -0,0 +1,38 @@ +{ + config, + lib, + ... +}: +{ + + options.common.security.networking.enable = lib.mkEnableOption "enhanced networking security"; + + config = lib.mkIf config.common.security.networking.enable { + boot.kernel.sysctl = { + # Enable strict reverse path filtering (that is, do not attempt to route + # packets that "obviously" do not belong to the iface's network; dropped + # packets are logged as martians). + "net.ipv4.conf.all.log_martians" = lib.mkDefault true; + "net.ipv4.conf.all.rp_filter" = lib.mkDefault "1"; + "net.ipv4.conf.default.log_martians" = lib.mkDefault true; + "net.ipv4.conf.default.rp_filter" = lib.mkDefault "1"; + + # Ignore broadcast ICMP (mitigate SMURF) + "net.ipv4.icmp_echo_ignore_broadcasts" = lib.mkDefault true; + + # Ignore incoming ICMP redirects (note: default is needed to ensure that the + # setting is applied to interfaces added after the sysctls are set) + "net.ipv4.conf.all.accept_redirects" = lib.mkDefault false; + "net.ipv4.conf.all.secure_redirects" = lib.mkDefault false; + "net.ipv4.conf.default.accept_redirects" = lib.mkDefault false; + "net.ipv4.conf.default.secure_redirects" = lib.mkDefault false; + "net.ipv6.conf.all.accept_redirects" = lib.mkDefault false; + "net.ipv6.conf.default.accept_redirects" = lib.mkDefault false; + + # Ignore outgoing ICMP redirects (this is ipv4 only) + "net.ipv4.conf.all.send_redirects" = lib.mkDefault false; + "net.ipv4.conf.default.send_redirects" = lib.mkDefault false; + }; + }; + +}
diff --git a/nixosModules/common/security/nix.nix b/nixosModules/common/security/nix.nix @@ -0,0 +1,19 @@ +{ + config, + lib, + ... +}: +{ + + options.common.security.nix.enable = lib.mkEnableOption "enhanced nix security"; + + config = lib.mkIf config.common.security.nix.enable { + users.groups.nix = { }; + + nix.settings.allowed-users = lib.mkForce [ + "@users" + "@nix" + ]; + }; + +}
diff --git a/nixosModules/common/services/NetworkManager.nix b/nixosModules/common/services/NetworkManager.nix @@ -0,0 +1,19 @@ +{ + config, + lib, + ... +}: +{ + + options.common.services.NetworkManager.enable = lib.mkEnableOption "NetworkManager"; + + config = lib.mkIf config.common.services.NetworkManager.enable { + common.configure.persist.system.dirs = [ + "/var/lib/NetworkManager" + "/etc/NetworkManager/system-connections" + ]; + + networking.networkmanager.enable = true; + }; + +}
diff --git a/nixosModules/common/services/gitolite.nix b/nixosModules/common/services/gitolite.nix @@ -0,0 +1,241 @@ +{ + config, + lib, + pkgs, + ... +}: +let + inherit (lib) types; + cfg = config.common.services.gitolite; + +in +{ + + options.common.services.gitolite = { + enable = lib.mkEnableOption "gitolite git server"; + + dataDir = lib.mkOption { + type = types.str; + default = "/var/lib/gitolite"; + }; + + adminPubkey = lib.mkOption { + type = types.str; + }; + + commonHooks = lib.mkOption { + type = types.attrsOf types.lines; + default = { }; + }; + + triggers = lib.mkOption { + type = types.attrsOf types.lines; + default = { }; + }; + + commands = lib.mkOption { + type = types.attrsOf types.lines; + default = { }; + }; + + extraGitoliteRc = lib.mkOption { + type = types.lines; + default = ""; + }; + + user = lib.mkOption { + type = types.str; + default = "gitolite"; + }; + + group = lib.mkOption { + type = types.str; + default = "gitolite"; + }; + }; + + config = lib.mkIf cfg.enable ( + let + # Use writeTextDir to not leak Nix store hash into file name + pubkeyFile = (pkgs.writeTextDir "gitolite-admin.pub" cfg.adminPubkey) + "/gitolite-admin.pub"; + hooks = lib.mapAttrs ( + name: script: + (pkgs.writeShellScript name ( + if name == "post-receive" then + '' + read oldrev newrev ref + [ -t 0 ] || cat >/dev/null + [ -z "$GL_REPO" ] && die GL_REPO not set + + '' + + script + else + script + )) + ) cfg.commonHooks; + triggers = lib.mapAttrs (name: script: (pkgs.writeShellScript name script)) cfg.triggers; + commands = lib.mapAttrs (name: script: (pkgs.writeShellScript name script)) cfg.commands; + manageGitoliteRc = cfg.extraGitoliteRc != ""; + rcDir = pkgs.runCommand "gitolite-rc" { + preferLocalBuild = true; + } rcDirScript; + rcDirScript = '' + mkdir "$out" + export HOME=temp-home + mkdir -p "$HOME/.gitolite/logs" # gitolite can't run without it + '${pkgs.gitolite}'/bin/gitolite print-default-rc >>"$out/gitolite.rc.default" + cat <<END >>"$out/gitolite.rc" + # This file is managed by NixOS. + # Use services.gitolite options to control it. + + END + cat "$out/gitolite.rc.default" >>"$out/gitolite.rc" + '' + + lib.optionalString (cfg.extraGitoliteRc != "") '' + echo -n ${lib.escapeShellArg '' + + # Added by NixOS: + ${lib.removeSuffix "\n" cfg.extraGitoliteRc} + + # per perl rules, this should be the last line in such a file: + 1; + ''} >>"$out/gitolite.rc" + ''; + + in + { + common.configure.persist.system.dirs = [ + { + directory = cfg.dataDir; + mode = "0755"; + inherit (cfg) user; + inherit (cfg) group; + } + ]; + + common.services.gitolite.extraGitoliteRc = '' + $RC{LOCAL_CODE} = "$ENV{HOME}/.gitolite/local"; + ''; + + users.users.${cfg.user} = { + home = cfg.dataDir; + uid = config.ids.uids.gitolite; + inherit (cfg) group; + extraGroups = [ "ssh" ]; + useDefaultShell = true; + packages = [ + pkgs.gitolite + pkgs.git + pkgs.perl + ]; + }; + + users.groups.${cfg.group}.gid = config.ids.gids.gitolite; + + systemd.services.gitolite-init = { + description = "Gitolite initialization"; + wantedBy = [ "multi-user.target" ]; + unitConfig.RequiresMountsFor = cfg.dataDir; + + environment = { + GITOLITE_RC = ".gitolite.rc"; + GITOLITE_RC_DEFAULT = "${rcDir}/gitolite.rc.default"; + }; + + serviceConfig = lib.mkMerge [ + (lib.mkIf (cfg.dataDir == "/var/lib/gitolite") { + StateDirectory = "gitolite gitolite/.gitolite gitolite/.gitolite/logs"; + StateDirectoryMode = "0750"; + }) + { + Type = "oneshot"; + User = cfg.user; + Group = cfg.group; + WorkingDirectory = "~"; + RemainAfterExit = true; + } + ]; + + path = [ + pkgs.gitolite + pkgs.git + pkgs.perl + pkgs.bash + pkgs.diffutils + config.programs.ssh.package + ]; + script = + let + rcSetupScriptIfCustomFile = + if manageGitoliteRc then + '' + cat <<END + <3>ERROR: NixOS can't apply declarative configuration + <3>to your .gitolite.rc file, because it seems to be + <3>already customized manually. + <3>See the services.gitolite.extraGitoliteRc option + <3>in "man configuration.nix" for more information. + END + # Not sure if the line below addresses the issue directly or just + # adds a delay, but without it our error message often doesn't + # show up in `systemctl status gitolite-init`. + journalctl --flush + exit 1 + '' + else + '' + : + ''; + rcSetupScriptIfDefaultFileOrStoreSymlink = + if manageGitoliteRc then + '' + ln -sf "${rcDir}/gitolite.rc" "$GITOLITE_RC" + '' + else + '' + [[ -L "$GITOLITE_RC" ]] && rm -f "$GITOLITE_RC" + ''; + in + '' + if ( [[ ! -e "$GITOLITE_RC" ]] && [[ ! -L "$GITOLITE_RC" ]] ) || + ( [[ -f "$GITOLITE_RC" ]] && diff -q "$GITOLITE_RC" "$GITOLITE_RC_DEFAULT" >/dev/null ) || + ( [[ -L "$GITOLITE_RC" ]] && [[ "$(readlink "$GITOLITE_RC")" =~ ^/nix/store/ ]] ) + then + '' + + rcSetupScriptIfDefaultFileOrStoreSymlink + + '' + else + '' + + rcSetupScriptIfCustomFile + + '' + fi + + if [ ! -d repositories ]; then + gitolite setup -pk ${pubkeyFile} + fi + + rm -rf .gitolite/local/hooks/common/ + mkdir -p .gitolite/local/hooks/common/ + ${lib.concatStringsSep "\n" ( + lib.mapAttrsToList (name: script: "ln -s ${script} .gitolite/local/hooks/common/${name}") hooks + )} + + rm -rf .gitolite/local/triggers/ + mkdir -p .gitolite/local/triggers/ + ${lib.concatStringsSep "\n" ( + lib.mapAttrsToList (name: script: "ln -s ${script} .gitolite/local/triggers/${name}") triggers + )} + + rm -rf .gitolite/local/commands/ + mkdir -p .gitolite/local/commands/ + ${lib.concatStringsSep "\n" ( + lib.mapAttrsToList (name: script: "ln -s ${script} .gitolite/local/commands/${name}") commands + )} + + gitolite setup # Upgrade if needed + ''; + }; + } + ); + +}
diff --git a/nixosModules/common/services/gotosocial.nix b/nixosModules/common/services/gotosocial.nix @@ -0,0 +1,248 @@ +{ + config, + pkgs, + lib, + ... +}: +let + inherit (lib) types; + cfg = config.common.services.gotosocial; + + settingsFormat = pkgs.formats.yaml { }; + defaultSettings = { + # Defaults + application-name = "gotosocial"; + protocol = "https"; + + bind-address = "127.0.0.1"; + port = 8080; + + user = cfg.user; + group = cfg.group; + + storage-local-base-path = cfg.stateDir; + + db-type = "sqlite"; + db-address = "${cfg.stateDir}/database.sqlite"; + }; + +in +{ + + meta.maintainers = with lib.maintainers; [ zaphyra ]; + + options.common.services.gotosocial = { + enable = lib.mkEnableOption "ActivityPub social network server"; + + package = lib.mkPackageOption pkgs "gotosocial" { }; + + user = lib.mkOption { + type = types.str; + default = "gotosocial"; + description = "The user gotosocial should run as"; + }; + + group = lib.mkOption { + type = types.str; + default = "gotosocial"; + description = "The group gotosocial should run as"; + }; + + stateDir = lib.mkOption { + type = types.str; + default = "/var/lib/gotosocial"; + readOnly = true; + }; + + setupPostgresqlDB = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Whether to setup a local postgres database and populate the + `db-type` fields in `services.gotosocial.settings`. + ''; + }; + + environmentFile = lib.mkOption { + type = with types; nullOr path; + default = null; + description = '' + File path containing environment variables for configuring the GoToSocial service + in the format of an EnvironmentFile as described by {manpage}`systemd.exec(5)`. + + This option could be used to pass sensitive configuration to the GoToSocial daemon. + + Please refer to the Environment Variables section in the [documentation](https://docs.gotosocial.org/en/latest/configuration/#environment-variables). + ''; + }; + + settings = lib.mkOption { + default = defaultSettings; + description = '' + Contents of the GoToSocial YAML config. + + Please refer to the + [documentation](https://docs.gotosocial.org/en/latest/configuration/) + and + [example config](https://github.com/superseriousbusiness/gotosocial/blob/main/example/config.yaml). + + Please note that the `host` option cannot be changed later so it is important to configure this correctly before you start GoToSocial. + ''; + type = types.submodule { + freeformType = settingsFormat.type; + options = { + host = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = '' + Hostname that this server will be reachable at. Defaults to localhost for local testing, + but you should *definitely* change this when running for real, or your server won't work at all. + DO NOT change this after your server has already run once, or you will break things! + ''; + }; + port = lib.mkOption { + type = lib.types.port; + default = 8080; + description = '' + Int. Listen port for the GoToSocial webserver + API. If you're running behind a reverse proxy and/or in a docker, + container, just set this to whatever you like (or leave the default), and make sure it's forwarded properly. + If you are running with built-in letsencrypt enabled, and running GoToSocial directly on a host machine, you will + probably want to set this to 443 (standard https port), unless you have other services already using that port. + This *MUST NOT* be the same as the letsencrypt port specified below, unless letsencrypt is turned off. + ''; + }; + }; + }; + }; + }; + + config = lib.mkIf cfg.enable ( + let + configFile = settingsFormat.generate "gotosocial-config.yaml" cfg.settings; + in + { + assertions = [ + { + assertion = cfg.settings.host or null != null; + message = "You have to define a hostname for GoToSocial (`common.services.gotosocial.settings.host`), it cannot be changed later without starting over!"; + } + ]; + + common.services.gotosocial.settings = + (lib.mapAttrs (name: lib.mkDefault) defaultSettings) + // { + web-template-base-dir = "${cfg.package}/share/gotosocial/web/template/"; + web-asset-base-dir = "${cfg.package}/share/gotosocal/web/assets/"; + } + // (lib.optionalAttrs cfg.setupPostgresqlDB { + db-type = "postgres"; + db-address = "/run/postgresql"; + db-database = "gotosocial"; + db-user = "gotosocial"; + }); + + users = { + users."${cfg.user}" = { + home = cfg.stateDir; + group = cfg.group; + isSystemUser = true; + }; + groups."${cfg.group}" = { }; + }; + + environment.etc."gotosocial.yaml".source = configFile; + + environment.systemPackages = [ + (pkgs.writeShellScriptBin "gotosocial-admin" '' + exec systemd-run \ + -u gotosocial-admin.service \ + -p Group=${cfg.group} \ + -p User=${cfg.user} \ + -q -t -G --wait --service-type=exec \ + ${lib.getExe cfg.package} --config-path ${configFile} admin "$@" + '') + ]; + + services.postgresql = lib.mkIf cfg.setupPostgresqlDB { + enable = true; + ensureDatabases = [ "gotosocial" ]; + ensureUsers = [ + { + name = "gotosocial"; + ensureDBOwnership = true; + } + ]; + }; + + systemd.services.gotosocial = { + description = "GoToSocial ActivityPub server"; + restartTriggers = [ configFile ]; + + wantedBy = [ "multi-user.target" ]; + requires = lib.optional cfg.setupPostgresqlDB "postgresql.service"; + after = [ + "network.target" + "sops-install-secrets.service" + ] + ++ lib.optional cfg.setupPostgresqlDB "postgresql.service"; + + environment = { + GTS_WAZERO_COMPILATION_CACHE = "${cfg.stateDir}/.cache"; + }; + + serviceConfig = { + User = cfg.user; + Group = cfg.group; + + Type = "exec"; + WorkingDirectory = cfg.stateDir; + StateDirectory = lib.mkIf ( + cfg.settings.storage-local-base-path != "/var/lib/gotosocial" + ) "gotosocial"; + ReadOnlyPaths = [ cfg.package ]; + ReadWritePaths = [ cfg.settings.storage-local-base-path ]; + StateDirectoryMode = "750"; + + Restart = "on-failure"; + RestartSec = 5; + + EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile; + ExecStart = "${lib.getExe cfg.package} --config-path ${configFile} server start"; + + # Security options: + # Based on https://github.com/superseriousbusiness/gotosocial/blob/v0.8.1/example/gotosocial.service + NoNewPrivileges = true; + PrivateTmp = true; + PrivateDevices = true; + + RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6"; + RestrictNamespaces = true; + RestrictRealtime = true; + + DevicePolicy = "closed"; + ProtectSystem = "full"; + ProtectControlGroups = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + LockPersonality = true; + + SystemCallFilter = "~@clock @debug @module @mount @obsolete @reboot @setuid @swap"; + + AmbientCapabilities = lib.optional (cfg.settings.port < 1024) "CAP_NET_BIND_SERVICE"; + CapabilityBoundingSet = [ + "~CAP_RAWIO CAP_MKNOD" + "~CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_AUDIT_WRITE" + "~CAP_SYS_BOOT CAP_SYS_TIME CAP_SYS_MODULE CAP_SYS_PACCT" + "~CAP_LEASE CAP_LINUX_IMMUTABLE CAP_IPC_LOCK" + "~CAP_BLOCK_SUSPEND CAP_WAKE_ALARM" + "~CAP_SYS_TTY_CONFIG" + "~CAP_MAC_ADMIN CAP_MAC_OVERRIDE" + "~CAP_NET_ADMIN CAP_NET_BROADCAST CAP_NET_RAW" + "~CAP_SYS_ADMIN CAP_SYS_PTRACE CAP_SYSLOG" + ]; + }; + }; + } + ); + +}
diff --git a/nixosModules/common/services/greetd.nix b/nixosModules/common/services/greetd.nix @@ -0,0 +1,74 @@ +{ + config, + lib, + pkgs, + ... +}: +let + inherit (lib) types; + cfg = config.common.services.greetd; + +in +{ + + options.common.services.greetd = { + enable = lib.mkEnableOption "greetd"; + autoLogin = { + enable = lib.mkOption { + type = types.bool; + default = false; + }; + user = lib.mkOption { type = types.str; }; + command = lib.mkOption { type = types.str; }; + }; + }; + + config = lib.mkIf cfg.enable { + security.pam.services.greetd.fprintAuth = false; + + common.configure.persist.system.dirs = [ + { + directory = "/var/cache/tuigreet"; + mode = "755"; + user = "greeter"; + group = "greeter"; + } + ]; + + systemd.tmpfiles.settings."greetd" = { + "/var/cache/tuigreet" = { + d = { + mode = "755"; + user = "greeter"; + group = "greeter"; + }; + }; + }; + + services.greetd = { + enable = true; + restart = true; + useTextGreeter = true; + settings = { + initial_session = lib.mkIf cfg.autoLogin.enable { inherit (cfg.autoLogin) user command; }; + default_session = { + user = "greeter"; + command = lib.concatStringsSep " " [ + (lib.getExe pkgs.tuigreet) + "--no-xsession-wrapper" + "--remember" + "--remember-session" + "--time" + "--user-menu" + "--asterisks" + "--power-shutdown" + "'systemctl poweroff'" + "--power-reboot" + "'systemctl reboot'" + ]; + }; + }; + }; + }; + +}
diff --git a/nixosModules/common/services/gvfs.nix b/nixosModules/common/services/gvfs.nix @@ -0,0 +1,56 @@ +{ + config, + lib, + pkgs, + ... +}: +let + inherit (lib) types; + cfg = config.common.services.gvfs; + +in +{ + + options.common.services.gvfs = { + enable = lib.mkEnableOption "gvfs"; + backends = { + afc = lib.mkOption { + type = types.bool; + default = false; + }; + afp = lib.mkOption { + type = types.bool; + default = false; + }; + mtp = lib.mkOption { + type = types.bool; + default = false; + }; + gphoto2 = lib.mkOption { + type = types.bool; + default = false; + }; + smb = lib.mkOption { + type = types.bool; + default = false; + }; + }; + }; + + config = lib.mkIf cfg.enable { + services.gvfs = { + enable = true; + package = + (pkgs.gvfs.overrideAttrs (old: { + mesonFlags = (old.mesonFlags or [ ]) ++ [ + "-Dafp=${lib.boolToString cfg.backends.afp}" + "-Dafc=${lib.boolToString cfg.backends.afc}" + "-Dmtp=${lib.boolToString cfg.backends.mtp}" + "-Dgphoto2=${lib.boolToString cfg.backends.gphoto2}" + ]; + })).override + { samba = null; }; + }; + }; + +}
diff --git a/nixosModules/common/services/mautrixBridge.nix b/nixosModules/common/services/mautrixBridge.nix @@ -0,0 +1,150 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + inherit (lib) types; + cfg = config.common.services.mautrixBridge; + forEachInstance = + f: lib.flip lib.mapAttrs' cfg (name: cfg: lib.nameValuePair "mautrixBridge-${name}" (f name cfg)); + +in +{ + + # todo: add some documentation + options.common.services.mautrixBridge = lib.mkOption { + default = { }; + type = types.attrsOf ( + types.submodule { + options = { + enable = lib.mkOption { + type = types.bool; + default = false; + }; + package = lib.mkOption { + type = types.package; + }; + settings = lib.mkOption { + type = (pkgs.formats.json { }).type; + default = { }; + }; + environmentFile = lib.mkOption { + type = with types; nullOr path; + default = null; + }; + serviceDependencies = lib.mkOption { + type = with types; listOf str; + default = [ ]; + }; + }; + } + ); + }; + + config = { + systemd.services = forEachInstance ( + name: cfg: + let + dataDir = "/var/lib/mautrix-${name}"; + registrationFile = "${dataDir}/registration.yaml"; + settingsFile = "${dataDir}/config.yaml"; + settingsFileUnsubstituted = + (pkgs.formats.json { }).generate "mautrix-${name}-config-unsubstituted.json" + cfg.settings; + in + { + enable = cfg.enable; + description = "mautrixBridge-${name}, a matrix puppeting bridge."; + restartTriggers = [ settingsFileUnsubstituted ]; + + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ] ++ cfg.serviceDependencies; + after = [ "network-online.target" ] ++ cfg.serviceDependencies; + + path = [ pkgs.ffmpeg-headless ]; + + 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 + ${pkgs.envsubst}/bin/envsubst \ + -o '${settingsFile}' \ + -i '${settingsFileUnsubstituted}' + umask $old_umask + + # generate the appservice's registration file if absent + if [ ! -f '${registrationFile}' ]; then + ${lib.getExe cfg.package} \ + --generate-registration \ + --config='${settingsFile}' \ + --registration='${registrationFile}' + fi + chmod 640 ${registrationFile} + + umask 0177 + #todo: switch to `yq-go` + ${lib.getExe pkgs.yq} -s ' + .[0].appservice.as_token = (.[0].appservice.as_token // .[1].as_token) + | .[0].appservice.hs_token = (.[0].appservice.hs_token // .[1].hs_token) + ${lib.optionalString ( + name == "telegram" + ) " + | .[0].network.api_id = (.[0].network.api_id | tonumber) + "} + | .[0]' \ + '${settingsFile}' '${registrationFile}' > '${settingsFile}.tmp' + mv '${settingsFile}.tmp' '${settingsFile}' + umask $old_umask + ''; + + serviceConfig = { + Type = "exec"; + + DynamicUser = true; + User = "mautrixBridge-${name}"; + Group = "mautrixBridge-${name}"; + + EnvironmentFile = cfg.environmentFile; + StateDirectory = baseNameOf dataDir; + WorkingDirectory = dataDir; + UMask = 27; + + ExecStart = "${lib.getExe cfg.package} --no-update --config='${settingsFile}'"; + + Restart = "on-failure"; + RestartSec = "30s"; + + LockPersonality = true; + NoNewPrivileges = true; + MemoryDenyWriteExecute = lib.mkIf (name != "signal") true; + + PrivateDevices = true; + PrivateTmp = true; + PrivateUsers = true; + + ProtectSystem = "strict"; + ProtectClock = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectControlGroups = true; + + RestrictRealtime = true; + RestrictSUIDSGID = true; + + SystemCallArchitectures = "native"; + SystemCallErrorNumber = "EPERM"; + SystemCallFilter = [ "@system-service" ]; + }; + } + ); + }; + +}
diff --git a/nixosModules/common/services/openssh.nix b/nixosModules/common/services/openssh.nix @@ -0,0 +1,133 @@ +{ + config, + lib, + ... +}: +let + inherit (lib) types; + cfg = config.common.services.openssh; + +in +{ + + options.common.services.openssh = { + enable = lib.mkEnableOption "openssh server"; + enableRSASupport = lib.mkEnableOption "rsa support"; + port = lib.mkOption { + type = types.port; + default = 22; + }; + }; + + config = lib.mkIf cfg.enable { + users.groups = { + ssh = { + gid = 200; + }; + sftp = { + gid = 201; + }; + }; + + # this is required because the secrets need to be decryped before the users get created + # but the impermanence bind-mounts get created _after_ the user creation... + sops.age.sshKeyPaths = [ + ( + if config.common.configure.persist.system.enable then + "/persist/system/var/lib/sshd/ssh_host_ed25519_key" + else + "/var/lib/sshd/ssh_host_ed25519_key" + ) + ]; + + common.configure.persist.system.dirs = [ "/var/lib/sshd" ]; + + services.openssh = { + enable = true; + + # Use socket activation via systemd + startWhenNeeded = true; + + # Hostkeys + hostKeys = [ + { + type = "ed25519"; + path = "/persist/system/var/lib/sshd/ssh_host_ed25519_key"; + } + ]; + + ports = [ cfg.port ]; + + # TODO: Find out why the heck this kills my gpg-agent + # extraConfig = "HostCertificate /run/secrets/hostcert"; + + settings = { + + # Disable password authentication to enforce pubkey authentication + PasswordAuthentication = false; + + # Disable keyboardinteractive authentication + KbdInteractiveAuthentication = false; + + X11Forwarding = false; + + # Only allow users of the ssh and sftp groups to connect + AllowGroups = [ + "sftp" + "ssh" + ]; + + CASignatureAlgorithms = lib.concatStringsSep "," [ + "ssh-ed25519" + "sk-ssh-ed25519@openssh.com" + ]; + + HostBasedAcceptedAlgorithms = lib.concatStringsSep "," [ + "ssh-ed25519-cert-v01@openssh.com" + "sk-ssh-ed25519-cert-v01@openssh.com" + "ssh-ed25519" + "sk-ssh-ed25519@openssh.com" + ]; + + HostKeyAlgorithms = lib.concatStringsSep "," [ + "ssh-ed25519-cert-v01@openssh.com" + "sk-ssh-ed25519-cert-v01@openssh.com" + "ssh-ed25519" + "sk-ssh-ed25519@openssh.com" + ]; + + PubKeyAcceptedAlgorithms = lib.concatStringsSep "," ( + [ + "ssh-ed25519-cert-v01@openssh.com" + "sk-ssh-ed25519-cert-v01@openssh.com" + "ssh-ed25519" + "sk-ssh-ed25519@openssh.com" + ] + ++ (lib.optionals cfg.enableRSASupport [ "rsa-sha2-512" ]) + ); + + # Specifies the available KEX (Key Exchange) algorithms + KexAlgorithms = [ + "mlkem768x25519-sha256" + "sntrup761x25519-sha512" + "sntrup761x25519-sha512@openssh.com" + "curve25519-sha256" + "curve25519-sha256@libssh.org" + ]; + + # Specifies the available MAC (message authentication code) algorithms + Macs = [ + "hmac-sha2-512-etm@openssh.com" + "hmac-sha2-256-etm@openssh.com" + ]; + + Ciphers = [ + "chacha20-poly1305@openssh.com" + "aes256-gcm@openssh.com" + "aes256-ctr" + ]; + }; + }; + }; + +}
diff --git a/nixosModules/common/services/power-profiles-daemon.nix b/nixosModules/common/services/power-profiles-daemon.nix @@ -0,0 +1,22 @@ +{ + config, + lib, + ... +}: +let + cfg = config.common.services.power-profiles-daemon; + +in +{ + + options.common.services.power-profiles-daemon.enable = lib.mkEnableOption "power-profiles-daemon"; + + config = lib.mkIf cfg.enable { + common.configure.persist.system.dirs = [ + "/var/lib/power-profiles-daemon" + ]; + + services.power-profiles-daemon.enable = true; + }; + +}
diff --git a/nixosModules/common/services/prosody.nix b/nixosModules/common/services/prosody.nix @@ -0,0 +1,148 @@ +{ + config, + lib, + ... +}: +let + inherit (lib) types; + cfg = config.common.services.prosody; + +in +{ + + options.common.services.prosody = { + enable = lib.mkEnableOption "prosody xmpp server with sane defaults"; + domain = lib.mkOption { + type = types.str; + }; + adminUsers = lib.mkOption { + type = with types; listOf str; + default = [ ]; + }; + }; + + config = lib.mkIf cfg.enable { + dns.zones."${cfg.domain}" = { + SRV = [ + { + proto = "tcp"; + service = "xmpp-server"; + priority = 0; + weight = 5; + port = 5269; + target = "xmpp.${cfg.domain}."; + } + { + proto = "tcp"; + service = "xmpp-client"; + priority = 0; + weight = 5; + port = 5222; + target = "xmpp.${cfg.domain}."; + } + ]; + subdomains = { + "muc.xmpp".SRV = [ + { + proto = "tcp"; + service = "xmpp-server"; + priority = 0; + weight = 5; + port = 5269; + target = "xmpp.${cfg.domain}."; + } + ]; + "upload.xmpp".CNAME = [ "${config.networking.fqdn}." ]; + xmpp.CNAME = [ "${config.networking.fqdn}." ]; + }; + }; + + networking.firewall.allowedTCPPorts = [ + 5269 + 5222 + ] + ++ config.services.prosody.httpsPorts; + + common.configure.persist.system.dirs = lib.singleton { + directory = config.services.prosody.dataDir; + mode = "755"; + inherit (config.services.prosody) user; + inherit (config.services.prosody) group; + }; + + security.acme.certs."${cfg.domain}" = { + inherit (config.services.prosody) group; + reloadServices = [ "prosody.service" ]; + extraDomainNames = [ + "xmpp.${cfg.domain}" + "muc.xmpp.${cfg.domain}" + "upload.xmpp.${cfg.domain}" + ]; + }; + + services.prosody = { + enable = true; + + allowRegistration = false; + authentication = "internal_hashed"; + admins = cfg.adminUsers; + + # c2sRequireEncryption = true; + # s2sSecureAuth = true; + + modules = { + disco = true; # Service discovery + roster = true; # Allow users to have a roster. Recommended ;) + saslauth = true; # Authentication for clients and servers. Recommended if you want to log in. + tls = true; # Add support for secure TLS on c2s/s2s connections + blocklist = true; # Allow users to block communications with other users + bookmarks = true; # Synchronise the list of open rooms between clients + carbons = true; # Keep multiple online clients in sync + dialback = true; # Support for verifying remote servers using DNS + limits = true; # Enable bandwidth limiting for XMPP connections + pep = true; # Allow users to store public and private data in their account + private = true; # Private XML storage (for room bookmarks, etc.) + smacks = true; # Allow a client to resume a disconnected session, and prevent message loss + vcard = true; # User profiles (stored in PEP) + vcard_legacy = true; # Conversion between legacy vCard and PEP Avatar, vcard + csi = true; # Allows clients to report their active/inactive state + ping = true; # Replies to XMPP pings with pongs + register = true; # Allow users to register on this server using a client and change passwords + time = true; # Let others know the time here on this server + uptime = true; # Report how long server has been running + version = true; # Replies to server version requests + mam = true; # Store recent messages to allow multi-device synchronization + }; + + extraModules = [ + "admin_adhoc" # Allows administration via an XMPP client that supports ad-hoc commands + "admin_shell" # Allow secure administration via 'prosodyctl shell' + "invites" # Create and manage invites + "invites_adhoc" # Allow admins/users to create invitations via their client + "invites_register" # Allows invited users to create accounts + ]; + + httpsPorts = [ 5281 ]; + httpFileShare.domain = "upload.xmpp.${cfg.domain}"; + + ssl.cert = "${config.security.acme.certs."${cfg.domain}".directory}/fullchain.pem"; + ssl.key = "${config.security.acme.certs."${cfg.domain}".directory}/key.pem"; + + virtualHosts.zaphyra = { + enabled = true; + domain = cfg.domain; + ssl.cert = "${config.security.acme.certs."${cfg.domain}".directory}/fullchain.pem"; + ssl.key = "${config.security.acme.certs."${cfg.domain}".directory}/key.pem"; + }; + + muc = [ + { + domain = "muc.xmpp.${cfg.domain}"; + name = "zaphyra Chat"; + restrictRoomCreation = "local"; + } + ]; + }; + }; + +}
diff --git a/nixosModules/common/services/resticBackup.nix b/nixosModules/common/services/resticBackup.nix @@ -0,0 +1,178 @@ +{ + config, + lib, + pkgs, + ... +}: +let + inherit (lib) types; + cfg = config.common.services.resticBackup; + +in +{ + + options.common.services.resticBackup = lib.mkOption { + default = { }; + type = types.attrsOf ( + types.submodule { + options = { + enable = lib.mkOption { + type = types.bool; + default = false; + }; + user = lib.mkOption { + type = types.str; + default = "root"; + }; + environmentFile = lib.mkOption { + type = with types; nullOr str; + default = null; + }; + passwordFile = lib.mkOption { + type = types.path; + }; + targets = lib.mkOption { + type = with types; listOf str; + }; + sshKeyFile = lib.mkOption { + type = types.path; + }; + timerConfig = lib.mkOption { + type = types.attrs; + default = { + OnCalendar = "daily"; + RandomizedDelaySec = 1200; + }; + }; + paths = lib.mkOption { + type = with types; listOf str; + default = [ ]; + }; + runBeforeBackup = lib.mkOption { + type = types.str; + default = ""; + }; + postgresDatabases = lib.mkOption { + type = with types; listOf str; + default = [ ]; + }; + sqliteDatabases = lib.mkOption { + type = with types; listOf str; + default = [ ]; + }; + influxBuckets = lib.mkOption { + type = with types; listOf str; + default = [ ]; + }; + exclude = lib.mkOption { + type = with types; listOf str; + default = [ ]; + }; + }; + } + ); + }; + + config = lib.mkIf (cfg != { }) { + systemd.services = lib.pipe cfg [ + lib.attrsToList + (lib.map ( + element: + ( + if element.value.enable then + (lib.map ( + target: + lib.nameValuePair "restic-backups-${element.name}-${target}" { + serviceConfig = { + LoadCredential = [ + "sshPrivateKey:${element.value.sshKeyFile}" + "repositoryPassword:${element.value.passwordFile}" + ]; + EnvironmentFile = lib.optionals (element.value.environmentFile != null) [ + element.value.environmentFile + ]; + }; + } + ) element.value.targets) + else + [ ] + ) + )) + lib.flatten + lib.listToAttrs + ]; + + services.restic.backups = lib.pipe cfg [ + lib.attrsToList + (lib.map ( + element: + ( + if element.value.enable then + (lib.map ( + target: + lib.nameValuePair "${element.name}-${target}" { + initialize = true; + inherit (element.value) user timerConfig exclude; + passwordFile = "%d/repositoryPassword"; + extraOptions = [ + "rclone.program=\"ssh -o StrictHostKeyChecking=no -i \${CREDENTIALS_DIRECTORY}/sshPrivateKey restic@${target}\"" + ]; + repository = "rclone:${element.name}"; + paths = lib.mkMerge [ + element.value.paths + (lib.mkIf (element.value.postgresDatabases != [ ]) ( + lib.map (element: "/tmp/postgresDatabases/${element}.sql.zst") element.value.postgresDatabases + )) + (lib.mkIf (element.value.sqliteDatabases != [ ]) ( + lib.map ( + element: "/tmp/sqliteDatabases/${builtins.baseNameOf element}.sqlite" + ) element.value.sqliteDatabases + )) + (lib.mkIf (element.value.influxBuckets != [ ]) ( + lib.map (element: "/tmp/influxBuckets/${element}") element.value.influxBuckets + )) + ]; + backupPrepareCommand = + element.value.runBeforeBackup + + + #dump postgresql databases + (lib.optionalString (element.value.postgresDatabases != [ ]) ( + lib.concatMapStringsSep "\n" (db: '' + echo "Dumping Postgres-database: ${db}" + mkdir -p /tmp/postgresDatabases + ${lib.getExe' pkgs.postgresql "pg_dump"} ${db} | ${lib.getExe pkgs.zstd} --rsyncable > /tmp/postgresDatabases/${db}.sql.zst + [ $(du -b /tmp/postgresDatabases/${db}.sql.zst | cut -f1) -gt "50" ] || exit 1 + '') element.value.postgresDatabases + )) + + + #dump sqlite databases + (lib.optionalString (element.value.sqliteDatabases != [ ]) ( + lib.concatMapStringsSep "\n" (db: '' + echo "Dumping sqlite-database: ${db}" + mkdir -p /tmp/sqliteDatabases + ${lib.getExe pkgs.sqlite} ${db} ".backup '/tmp/sqliteDatabases/${builtins.baseNameOf db}.sqlite'" + [ $(du -b /tmp/sqliteDatabases/${builtins.baseNameOf db}.sqlite | cut -f1) -gt "50" ] || exit 1 + '') element.value.sqliteDatabases + )) + + + #dump influx buckets + (lib.optionalString (element.value.influxBuckets != [ ]) ( + lib.concatMapStringsSep "\n" (db: '' + echo "Dumping influx-bucket: ${db}" + mkdir -p /tmp/influxBuckets + ${pkgs.influxdb2}/bin/influx backup --compression=none --bucket=${db} /tmp/influxBuckets/${db} + [ $(du -b /tmp/influxBuckets/${db} | cut -f1) -gt "50" ] || exit 1 + '') element.value.influxBuckets + )); + } + ) element.value.targets) + else + [ ] + ) + )) + lib.flatten + lib.listToAttrs + ]; + }; + +}
diff --git a/nixosModules/common/services/upower.nix b/nixosModules/common/services/upower.nix @@ -0,0 +1,20 @@ +{ + config, + lib, + ... +}: +let + cfg = config.common.services.upower; + +in +{ + + options.common.services.upower.enable = lib.mkEnableOption "upower"; + + config = lib.mkIf cfg.enable { + services.upower.enable = true; + + common.configure.persist.system.dirs = [ "/var/lib/upower" ]; + }; + +}
diff --git a/nixosModules/common/services/vnstat.nix b/nixosModules/common/services/vnstat.nix @@ -0,0 +1,155 @@ +{ + config, + lib, + pkgs, + ... +}: +let + inherit (lib) types; + cfg = config.common.services.vnstat; + +in +{ + + options.common.services.vnstat = { + enable = lib.mkEnableOption "vnstat"; + serveGraphs = lib.mkOption { + type = types.bool; + default = true; + }; + }; + + config = lib.mkIf cfg.enable { + services.vnstat.enable = true; + + common.configure.persist.system.dirs = [ "/var/lib/vnstat" ]; + + services.nginx.virtualHosts."${config.networking.fqdn}" = lib.mkIf cfg.serveGraphs { + locations."/vnstat/" = { + alias = "/var/run/vnstati/"; + index = "index.html"; + }; + }; + + systemd.services.vnstati = lib.mkIf cfg.serveGraphs { + wantedBy = [ "multi-user.target" ]; + after = [ "vnstat.service" ]; + startAt = "*-*-* *:0/10:00"; + + serviceConfig = { + User = "vnstatd"; + Group = "vnstatd"; + RuntimeDirectoryPreserve = true; + RuntimeDirectory = "vnstati"; + RuntimeDirectoryMode = 775; + PrivateTmp = true; + ProtectHome = true; + ProtectSystem = "strict"; + }; + + path = with pkgs; [ + vnstat + jq + nix + ]; + script = '' + set -x + ifaces=$(vnstat --json | jq -r .interfaces[].name | grep -v "^lo$") + echo $ifaces + + cat > /var/run/vnstati/index.html << EOF + <!DOCTYPE html> + <html lang="en"> + <head> + <title>${config.networking.hostName} - stats</title> + <meta charset="utf-8"> + <meta name="robots" content="none"> + <link rel="icon" href="static/favicon.ico" type="image/x-icon"> + <style> + body { + padding: 0; + margin: 0; + background: #eee; + font-family: monospace; + min-height: 100vh; + } + img:not(:last-child) { + margin-bottom: 15px; + } + img { + flex-shrink: 0; + } + .ui.segment { + margin: 0 1em 10px 1em; + padding: 1em; + background-color: white; + box-shadow: 0 1px 2px 0 rgba(34,36,38,.15); + border-radius: .286rem; + border: 1px solid rgba(34,36,38,.15); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + } + div.heading { + min-width: 100vw; + height: 70px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } + div.table.vertical { + overflow-y: auto; + min-width: 100vw; + height: calc(100vh - 70px); + display: flex; + flex-direction: row; + } + div.iface { + display: flex; + flex-direction: column; + align-items: center; + flex-grow: 1; + } + </style> + </head> + <body> + <div class="heading"> + <h1>${config.networking.hostName}</h1> + </div> + <div class="table vertical"> + EOF + + for iface in $ifaces + do + cat >> /var/run/vnstati/index.html << EOF + <div class="iface"> + <h3>''${iface}</h3> + <div class="ui segment"><img src="''${iface}-summary.png" alt="Summary"></div> + <div class="ui segment"><img src="''${iface}-hourly.png" alt="Hourly"></div> + <div class="ui segment"><img src="''${iface}-daily.png" alt="Daily"></div> + <div class="ui segment"><img src="''${iface}-monthly.png" alt="Monthly"></div> + <div class="ui segment"><img src="''${iface}-top10.png" alt="Top 10"></div> + </div> + EOF + + vnstati -s -nh -i $iface -o /var/run/vnstati/$iface-summary.png + vnstati -h -nh -i $iface -o /var/run/vnstati/$iface-hourly.png + vnstati -d -nh -i $iface -o /var/run/vnstati/$iface-daily.png + vnstati -m -nh -i $iface -o /var/run/vnstati/$iface-monthly.png + vnstati -t -nh -i $iface -o /var/run/vnstati/$iface-top10.png + done + + cat >> /var/run/vnstati/index.html << EOF + </div> + </body> + </html> + EOF + + ''; + }; + }; + +}
diff --git a/nixosModules/common/users/zaphyra.nix b/nixosModules/common/users/zaphyra.nix @@ -0,0 +1,36 @@ +{ + sopsSecrets, + config, + lib, + pkgs, + ... +}: + +{ + + options.common.users.zaphyra.enable = lib.mkEnableOption "zaphyra user"; + + config = lib.mkIf config.common.users.zaphyra.enable { + environment.systemPackages = [ pkgs.ghostty.terminfo ]; + + sops.secrets.zaphyraPassword = { + neededForUsers = true; + sopsFile = sopsSecrets.common; + }; + + users.users.zaphyra = { + uid = 1001; + description = "Katja"; + hashedPasswordFile = config.sops.secrets.zaphyraPassword.path; + isNormalUser = true; + extraGroups = [ + "ssh" + "wheel" + ]; + openssh.authorizedKeys.keys = [ + (builtins.readFile "${pkgs.zpha.website}/ssh_pubkey.asc") + ]; + }; + }; + +}
diff --git a/nixosModules/default.nix b/nixosModules/default.nix @@ -0,0 +1,24 @@ +{ + lib ? import "${(import ../npins).nixpkgs}/lib", +}: + +let + # read the current directorys files and pipe the result through a list of functions + modules = builtins.foldl' (x: f: f x) (builtins.readDir ./.) [ + # convert to a list containing just the attribute names + (builtins.attrNames) + # drop "default.nix" from the list + (builtins.filter (name: name != "default.nix")) + (builtins.map (name: { + inherit name; + value = lib.filesystem.listFilesRecursive ./${name}; + })) + # convert the resulting list to an attribute set + (builtins.listToAttrs) + ]; + +in +modules +// { + default = lib.flatten (builtins.attrValues modules); +}
diff --git a/nixosModules/dns/dns.nix b/nixosModules/dns/dns.nix @@ -0,0 +1,53 @@ +{ + nixosConfigurations, + config, + lib, + pkgs, + ... +}: +let + inherit (lib) types; + +in +{ + + options.dns = { + enable = lib.mkEnableOption "dns zones via nix"; + + # contains dns entries defined on the local host + zones = lib.mkOption { + type = types.attrsOf pkgs.dnsNix.types.subzone; + default = { }; + }; + + # contains dns entries defined on the local host and on remote hosts, merged together + allZones = lib.mkOption { + type = types.attrsOf pkgs.dnsNix.types.zone; + default = { }; + }; + + zoneFiles = lib.mkOption { + type = types.attrsOf types.path; + readOnly = true; + default = lib.mapAttrs ( + name: zone: + toString ( + pkgs.writeTextFile { + name = "${name}.zone"; + text = pkgs.dnsNix.types.zoneToString name (pkgs.dnsNix.evalZone name zone); + } + ) + ) config.dns.allZones; + }; + }; + + config = lib.mkIf config.dns.enable { + # serve records defined in all host configs + dns.allZones = lib.mkMerge ( + lib.flip lib.mapAttrsToList nixosConfigurations ( + _hostName: machineConfig: machineConfig.config.dns.zones + ) + ); + }; + +}
diff --git a/nixosModules/toBeUpstreamed/gomuks.nix b/nixosModules/toBeUpstreamed/gomuks.nix @@ -0,0 +1,199 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + inherit (lib) types; + + settingsFormat = pkgs.formats.yaml { }; + +in +{ + + options.services.gomuks = lib.mkOption { + description = "Configuration for gomuks instances."; + default = { }; + type = types.attrsOf ( + types.submodule { + options = { + enable = (lib.mkEnableOption "gomuks, a Matrix client written in Go.") // { + default = true; + }; + + package = lib.mkPackageOption pkgs "gomuks" { }; + + settings = lib.mkOption { + default = { }; + description = '' + Contents of the gomuks YAML config. + ''; + type = types.submodule { + freeformType = settingsFormat.type; + options = { + web = { + listen_address = lib.mkOption { + type = types.str; + default = "localhost:29325"; + description = '' + The address and port that gomuks will listen on. + ''; + }; + + username = lib.mkOption { + type = types.str; + description = '' + Username for basic http authentication. + ''; + }; + + password_hash = lib.mkOption { + type = types.str; + description = '' + `bcrypt`ed password for basic http authentication. + ''; + }; + + DisableAuthBecauseIWantMyAccountToBeHacked = lib.mkOption { + type = with types; nullOr bool; + visible = false; + default = null; + }; + }; + + logging.writers = lib.mkOption { + type = with types; listOf attrs; + visible = false; + default = [ + { + type = "stdout"; + format = "pretty-colored"; + } + ]; + }; + }; + }; + }; + }; + } + ); + }; + + config = { + assertions = lib.flatten ( + lib.flip lib.mapAttrsToList config.services.gomuks ( + name: cfg: [ + { + assertion = ( + (cfg.settings.web.DisableAuthBecauseIWantMyAccountToBeHacked != true) + && ((cfg.settings.web ? username) || (cfg.settings.web ? password_hash)) + ); + message = '' + You need to set at least `services.gomuks.${name}.settings.web.username` and + `services.gomuks.${name}.settings.web.password_hash` in the settings. + ''; + } + ] + ) + ); + + systemd.services = lib.flip lib.mapAttrs' config.services.gomuks ( + name: cfg: + (lib.nameValuePair "gomuks-${name}" ( + let + settingsFile = settingsFormat.generate "gomuks-${name}.yaml" ( + lib.filterAttrsRecursive (name: value: value != null) cfg.settings + ); + + in + { + enable = cfg.enable; + description = "A Matrix client written in Go"; + + restartTriggers = [ settingsFile ]; + + wantedBy = [ "multi-user.target" ]; + + environment = { + HOME = lib.mkDefault "%S/%N"; + GOMUKS_CONFIG_HOME = lib.mkDefault "%S/%N"; + GOMUKS_DATA_HOME = lib.mkDefault "%S/%N"; + GOMUKS_LOGS_HOME = lib.mkDefault "%S/%N"; + GOMUKS_CACHE_HOME = lib.mkDefault "%C/%N"; + GOMUKS_TMPDIR = lib.mkDefault "%C/%N"; + }; + + preStart = '' + test -f "$GOMUKS_CONFIG_HOME/config-nixos.yaml" && rm -f "$GOMUKS_CONFIG_HOME/config-nixos.yaml" + + ${lib.getExe pkgs.systemd-credsubst} \ + --copy-if-no-creds \ + --input "${settingsFile}" \ + --output "$GOMUKS_CONFIG_HOME/config-nixos.yaml" + + if [ -f "$GOMUKS_CONFIG_HOME/config.yaml" ]; + then + ${lib.getExe pkgs.yq-go} -i ". *= load(\"$GOMUKS_CONFIG_HOME/config-nixos.yaml\")" "$GOMUKS_CONFIG_HOME/config.yaml" + else + cp "$GOMUKS_CONFIG_HOME/config-nixos.yaml" "$GOMUKS_CONFIG_HOME/config.yaml" + fi + ''; + + serviceConfig = { + DynamicUser = true; + + StateDirectory = "gomuks-${name}"; + CacheDirectory = "gomuks-${name}"; + + ExecStart = lib.getExe cfg.package; + + ProtectSystem = "full"; + ProtectControlGroups = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + LockPersonality = true; + + DevicePolicy = "strict"; + DeviceAllow = [ "/dev/stdin r" ]; + + SystemCallArchitectures = "native"; + MemoryDenyWriteExecute = true; + + NoNewPrivileges = true; + PrivateTmp = true; + PrivateDevices = true; + UMask = "077"; + + RestrictAddressFamilies = "AF_INET AF_INET6"; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + + RemoveIPC = true; + PrivateIPC = true; + + SystemCallFilter = [ + "@system-service" + "~@resources" + "~@mount" + "~@setuid" + "~@cpu-emulation" + "~@debug" + "~@keyring" + "~@obsolete" + "~@pkey" + "setrlimit" + ]; + }; + } + )) + ); + }; + + meta.maintainers = with lib.maintainers; [ + zaphyra + ]; + +}
diff --git a/nixosModules/zpha/configure/cccdaWifi.nix b/nixosModules/zpha/configure/cccdaWifi.nix @@ -0,0 +1,43 @@ +{ + lib, + config, + pkgs, + ... +}: +{ + + options.zpha.configure.cccdaWifi.enable = + lib.mkEnableOption "configure cccda wifi NetworkManager profile"; + + config = lib.mkIf config.zpha.configure.cccdaWifi.enable { + networking.networkmanager.ensureProfiles = { + profiles.ccc-da-wifi = { + connection = { + id = "darmstadt.ccc.de"; + type = "wifi"; + uuid = "945c40f1-a800-4619-8276-1002a718a9f2"; + autoconnect = "true"; + }; + "wifi" = { + ssid = "darmstadt.ccc.de"; + mode = "infrastructure"; + }; + "wifi-security" = { + "auth-alg" = "open"; + "key-mgmt" = "wpa-eap"; + }; + "802-1x" = { + "eap" = "ttls"; + "phase2-auth" = "pap"; + "ca-cert" = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"; + "altsubject-matches" = "DNS:radius.cccda.de"; + "identity" = config.networking.fqdn; + "password" = config.networking.fqdn + (toString config.networking.hostId); + }; + ipv4.method = "auto"; + ipv6.method = "auto"; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/configure/dn42Router.nix b/nixosModules/zpha/configure/dn42Router.nix @@ -0,0 +1,208 @@ +{ + machineConfig, + config, + lib, + ... +}: + +{ + + options.zpha.configure.dn42Router.enable = lib.mkEnableOption ""; + + config = lib.mkIf config.zpha.configure.dn42Router.enable { + dns.zones."zaphyra.eu".subdomains."router-a.dn42".AAAA = [ + machineConfig.networking.ip6Address + ]; + + sops.secrets."dn42/wgPrivateKey" = { + owner = "systemd-network"; + group = "systemd-network"; + }; + + networking.firewall = { + checkReversePath = "loose"; + allowedUDPPorts = [ + config.systemd.network.netdevs."20-dn42".wireguardConfig.ListenPort + ]; + trustedInterfaces = [ + "dn42" + "wg-cautus" + ]; + }; + + systemd.network = { + config.networkConfig = { + IPv6Forwarding = true; + }; + }; + + common.configure.dn42Router = { + enable = true; + routerId = 42171801; + asn = 4242421718; + address = "fd6b:6174:6a61::1"; + range = "fd6b:6174:6a61::/48"; + peerings = { + kioubit = { + asn = 4242423914; + remoteLinkLocalAddress = "fe80::ade0"; + endpoint = "de2.g-load.eu:21718"; + publicKey = "B1xSG/XTJRLd+GrWDsB06BqnIq8Xud93YVh/LYYYtUY="; + listenPort = 51823; + }; + pleiades = { + asn = 4242420069; + remoteLinkLocalAddress = "fe80::706c:6569:6164:6573"; + endpoint = "central.net.nojus.org:21718"; + publicKey = "1YAga5Bhreysf/XmhOnDGh3FmbN3Mp2jZjMSAQb/TEM="; + listenPort = 51824; + }; + echonet = { + asn = 4242420714; + remoteLinkLocalAddress = "fe80::718"; + publicKey = "NxYj58YhWf0JXC+pQAHfh3saUkQSII0lBTDvYGe5kw4="; + listenPort = 51825; + }; + tbspace = { + asn = 76190; + remoteLinkLocalAddress = "fe80::1299:e"; + endpoint = "dn42.tbspace.de:49158"; + publicKey = "NW8IeEmAXmwYMuMlvrb9Zpkcko6bzotDlYtGePtgzQE="; + listenPort = 51826; + }; + antibldg = { + asn = 4242421403; + remoteLinkLocalAddress = "fe80::1234:9320"; + endpoint = "zaphyra.dn42.antibuild.ing:15569"; + publicKey = "vambITMGGpA7kxCRGFlY1X36bevxXYELT/ORNgZ72ms="; + listenPort = 51827; + }; + dahlabandon = { + asn = 4242420814; + remoteLinkLocalAddress = "fe80::1718"; + endpoint = "helios-fallen.iron-bear.de:1718"; + publicKey = "+tg4bDDwfyQZSw0x8x9Ye2tDWPZ/VAf+KTAE1QLaKEI="; + listenPort = 51828; + }; + pentane = { + asn = 4242423253; + remoteLinkLocalAddress = "fe80::43:59:43"; + endpoint = "imp.aidoskyneen.eu:49507"; + publicKey = "W+h0FMrxsAP7RppqFFMrfDHuu5CMW5aTW9E1MZXFf1w="; + listenPort = 51829; + }; + # e1mo = { + # asn = 4242420565; + # remoteLinkLocalAddress = ""; + # endpoint = ""; + # publicKey = ""; + # listenPort = 51830; + # }; + clerie = { + asn = 4242422574; + remoteLinkLocalAddress = "fe80::2574"; + endpoint = "dn42-il-gw1.net.clerie.de:51718"; + publicKey = "yJmr6lQzibmZV6/6VItXsXbcq4UKMyWFwJJt4lAkvCs="; + listenPort = 51831; + }; + etwas = { + asn = 4242422264; + remoteLinkLocalAddress = "fe80::acab"; + endpoint = "ncvps.dn42.etwas.me:22266"; + publicKey = "7ZLtBmXN+zOYJ52jtUdZO0HiEZZrxnIO/LLejFcFnnk="; + listenPort = 51832; + }; + pilz = { + asn = 4242420663; + remoteLinkLocalAddress = "fe80::acab"; + publicKey = "NxHkdwZPVL+3HdrHTFOslUpUckTf0dzEG9qpZ0FTBnA="; + listenPort = 51833; + }; + # c4tg1rl5 = { + # asn = "4242421411"; + # remoteLinkLocalAddress = ""; + # hasPresharedKey = true; + # publicKey = ""; + # listenPort = 51834; + # }; + lgcl = { + asn = 4242421825; + remoteLinkLocalAddress = "fe80::4d:6172:6379"; + publicKey = "7AoJ23hNMLzVM4jjusBlrGDEdwAkSdsEl3Vw/diVlns="; + listenPort = 51835; + }; + mira = { + asn = 4242420161; + endpoint = "dn42-router-1.svc.nesaia.net:21718"; + remoteLinkLocalAddress = "fe80::161"; + publicKey = "AAcJ/NA68NtxKd27+HlbEZXFo+u0NKt+ksUGYJBc/AQ="; + listenPort = 51836; + }; + lfm = { + asn = 4242420632; + endpoint = "gw-de1.dn42.infra.linfan.moe:41718"; + remoteLinkLocalAddress = "fe80::632"; + publicKey = "WfQGnN2WX5KTi3SGIde2kuVfhgDQ+ASaZfU+ORb9ukA="; + listenPort = 51837; + }; + ember = { + asn = 4242420197; + endpoint = "chaldene.dn42.n0emis.eu:21718"; + remoteLinkLocalAddress = "fe80::42:42:1"; + publicKey = "UgGpP9j881KeHOL5qElEZ/fXbucBUgTcihbv3yGlxHo="; + listenPort = 51838; + }; + }; + }; + + zpha.profiles.dn42 = { + enable = true; + wgPublicKey = machineConfig.networking.dn42.wgPublicKey; + wgPrivateKey = config.sops.secrets."dn42/wgPrivateKey".path; + addresses = [ + "${machineConfig.networking.dn42.ip6Address}/${toString machineConfig.networking.dn42.ip6PrefixLength}" + "fd6b:6174:6a61:53::${toString machineConfig.id}/128" + ]; + }; + + services.kresd = { + enable = true; + listenPlain = [ "[fd6b:6174:6a61::1]:53" ]; + extraConfig = '' + modules = { + 'hints > iterate', -- Allow loading /etc/hosts or custom root hints + 'stats', -- Track internal statistics + 'predict', -- Prefetch expiring/frequent records + } + + log_level('info') + + -- Cache size + cache.size = 100 * MB + + dn42 = { + 'dn42.', + '20.172.in-addr.arpa.', + '21.172.in-addr.arpa.', + '22.172.in-addr.arpa.', + '23.172.in-addr.arpa.', + '10.in-addr.arpa.', + 'd.f.in-addr.arpa.', + } + + -- NXDOMAINs that could sometimes happen due to aggressive DNSSEC caching. + policy.add(policy.suffix(policy.FLAGS({'NO_EDNS'}), dn42)) + + policy.add(policy.suffix(policy.STUB({'fd42:d42:d42:54::1', 'fd42:d42:d42:53::1'}), policy.todnames(dn42))) + -- policy.add(policy.FORWARD({'1.1.1.1'})) + + -- trust_anchors.remove('.') + trust_anchors.set_insecure(dn42) -- Disable DNSSEC for these domains + + modules.load('nsid') + nsid.name(hostname() .. ':' .. os.getenv("SYSTEMD_INSTANCE")) + ''; + }; + }; + +}
diff --git a/nixosModules/zpha/configure/dnsServer.nix b/nixosModules/zpha/configure/dnsServer.nix @@ -0,0 +1,156 @@ +{ + machines, + config, + lib, + pkgs, + ... +}: +let + inherit (lib) types; + cfg = config.zpha.configure.dnsServer; + +in +{ + + options.zpha.configure.dnsServer = { + enable = lib.mkEnableOption ""; + isPrimary = lib.mkOption { + type = types.bool; + default = config.networking.hostName == "cautus"; + }; + }; + + config = lib.mkIf cfg.enable ( + let + allZones = + with pkgs.dnsNix.combinators; + let + TXT = [ "openpgp4fpr:BFE6386C8D66BCD4DAE14FC895F0FE7CD7E6A022" ]; + CAA = [ + { + issuerCritical = false; + tag = "issue"; + value = "letsencrypt.org"; + } + ]; + NS = [ + "ns1.fc9f.de." + "ns2.fc9f.de." + ]; + SOA = { + nameServer = "ns1.fc9f.de."; + adminEmail = "dns@fc9f.de"; # Email address with a real `@`! + serial = 0; + }; + + in + { + "fc9f.de" = { + inherit NS SOA; + subdomains = { + acme.NS = [ "ns1" ]; + ns1 = host machines.cautus.networking.ip4Address machines.cautus.networking.ip6Address; + ns2 = host machines.sorrah.networking.ip4Address machines.sorrah.networking.ip6Address; + }; + }; + + "zaphyra.dn42" = { + NS = [ "ns1.zaphyra.dn42." ]; + SOA = { + nameServer = "ns1.zaphyra.dn42."; + adminEmail = "dns@zaphyra.eu"; # Email address with a real `@`! + serial = 0; + }; + subdomains = { + ns1.AAAA = [ "fd6b:6174:6a61:53::1" ]; + ns2.AAAA = [ "fd6b:6174:6a61:53::2" ]; + }; + }; + + "zaphyra.eu" = { + inherit + SOA + NS + CAA + ; + }; + + "zaphyra.de" = { + inherit + SOA + NS + CAA + TXT + ; + }; + + "zpha.de" = { + inherit + SOA + NS + CAA + TXT + ; + }; + + "katja.wtf" = { + inherit SOA NS CAA; + }; + + "ctucx.de" = { + inherit SOA NS CAA; + }; + + "thein.ovh" = { + inherit SOA NS CAA; + }; + + "ctu.cx" = { + inherit SOA NS CAA; + subdomains = { + _atproto.TXT = [ "did=did:plc:zaeuok3fmh2pcp4cjiicku4i" ]; + }; + }; + }; + + in + { + systemd.services.knot.after = lib.mkIf cfg.isPrimary [ "sops-install-secrets.service" ]; + + sops.secrets.knotKeys = lib.mkIf cfg.isPrimary { + owner = "knot"; + group = "knot"; + }; + + dns = { + enable = true; + inherit allZones; + }; + + zpha.services = { + knot = { + enable = true; + primary = cfg.isPrimary; + zones = lib.mkIf cfg.isPrimary ( + lib.mapAttrs (_name: value: { + file = value; + journal-content = "all"; + zonefile-sync = -1; + zonefile-load = "difference-no-serial"; + }) config.dns.zoneFiles + ); + }; + knotACME = lib.mkIf cfg.isPrimary { + enable = true; + zone = "acme.fc9f.de"; + zones = lib.attrNames allZones; + nameServers = [ + "ns1.fc9f.de." + ]; + keyFile = config.sops.secrets.knotKeys.path; + }; + }; + } + ); + +}
diff --git a/nixosModules/zpha/configure/floraCtl.nix b/nixosModules/zpha/configure/floraCtl.nix @@ -0,0 +1,92 @@ +{ + config, + lib, + ... +}: + +{ + + options.zpha.configure.floraCtl.enable = lib.mkEnableOption ""; + + config = lib.mkIf config.zpha.configure.floraCtl.enable { + sops.secrets."zigbee2mqttSecrets.yaml" = { + owner = "zigbee2mqtt"; + key = "zigbee2mqttSecrets"; + }; + + common.configure.persist.system.dirs = [ + { + directory = "/var/lib/zigbee2mqtt"; + mode = "0770"; + user = "zigbee2mqtt"; + group = "zigbee2mqtt"; + } + ]; + + systemd.services.zigbee2mqtt = { + serviceConfig = { + Restart = lib.mkForce "always"; + RuntimeMaxSec = "1d"; + }; + }; + + services = { + udev.extraRules = '' + SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{serial}=="00_12_4B_00_25_9B_C1_FC", SYMLINK+="zigbee0" + ATTR{idVendor}=="0451", ATTR{idProduct}=="16a8", ENV{ID_MM_DEVICE_IGNORE}="1" + SUBSYSTEM=="tty", ATTRS{idVendor}=="0451", ATTRS{idProduct}=="16a8", SYMLINK+="zigbee0" + ''; + + zigbee2mqtt = { + enable = true; + settings = { + homeassistant.enabled = true; + permit_join = false; + + mqtt = { + base_topic = "zigbee2mqtt"; + server = "mqtt://192.168.2.147:1883"; + user = "!${config.sops.secrets."zigbee2mqttSecrets.yaml".path} mqttUser"; + password = "!${config.sops.secrets."zigbee2mqttSecrets.yaml".path} mqttPassword"; + }; + + serial = { + adapter = "zstack"; + port = "/dev/zigbee0"; + disable_led = true; + }; + + frontend = { + port = 8422; + host = "::1"; + }; + + advanced = { + log_level = "info"; + log_output = [ "console" ]; + channel = 11; + pan_id = 55067; + ext_pan_id = [ + 116 + 73 + 103 + 101 + 20 + 101 + 160 + 9 + ]; + network_key = "!${config.sops.secrets."zigbee2mqttSecrets.yaml".path} network_key"; + }; + + device_options.retain = true; + devices = { + "0x00124b0023ad17f1".friendly_name = "door_buzzer"; + }; + }; + }; + }; + + }; + +}
diff --git a/nixosModules/zpha/configure/fonts.nix b/nixosModules/zpha/configure/fonts.nix @@ -0,0 +1,36 @@ +{ + lib, + config, + pkgs, + ... +}: +{ + + options.zpha.configure.fonts.enable = lib.mkEnableOption "configure fonts"; + + config = lib.mkIf config.zpha.configure.fonts.enable { + fonts = { + fontconfig = { + enable = true; + defaultFonts = { + serif = [ "Adwaita Sans" ]; + sansSerif = [ "Adwaita Sans" ]; + monospace = [ "Adwaita Mono" ]; + emoji = [ "Noto Color Emoji" ]; + }; + }; + + packages = with pkgs; [ + adwaita-fonts + liberation_ttf + ttf_bitstream_vera + noto-fonts + noto-fonts-color-emoji + nerd-fonts.symbols-only + fira-code + fira-mono + ]; + }; + }; + +}
diff --git a/nixosModules/zpha/configure/mailServer.nix b/nixosModules/zpha/configure/mailServer.nix @@ -0,0 +1,266 @@ +{ + npins, + sopsSecrets, + machineConfig, + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.configure.mailServer.enable = lib.mkEnableOption ""; + + imports = + (lib.mkIf config.zpha.configure.mailServer.enable [ + (import npins.simpleNixosMailserver) + ]).content; + + config = lib.mkIf config.zpha.configure.mailServer.enable ( + let + hostName = config.networking.fqdn; + ip6Address = machineConfig.networking.ip6Address; + ip4Address = machineConfig.networking.ip4Address; + primaryDomain = "zaphyra.eu"; + primaryDomainDkimKey = "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMuEtG24S6ksVx04avtjwIrfijZvQMxe44HrAXjW+Qe7ZbBHtS+q8alvL21zHbe4VgAOTNZ+fCnqSif4TFaOQnwuGwWke5SRBHV6RmWLaJUnN7krjFj+oNmKnl5M3GPI62shhk4OlMgAdDrH/JApd4XTqR3m0U/8rXqPumfbHhzwIDAQAB"; + extraDomains = { + "katja.wtf" = + "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC56L/GDEY0hTvcZjjCpk3/3c4qAmGAQR06tVpgz7gHs0kMPiGhpg3gDv8kCOu4l3C96oT6eaQoyLcC+ZhJT4ribZaNSD+7lkXk23s4LecklBQxLAjvLrc0GQ9zYp8/Qg4g+Wo9fHR3Lum4tqyyFuT/P21knw+nDWxvz3d0Y4XNVwIDAQAB"; + "ctu.cx" = + "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOuE5vBNP0L4i3OxcFdFbTJ/c8o58CL+cMHBh8lAZej1nSYOPBdpfJRpWiHduWu8cLWNu62nDeY9IGGnE6g9o6+6sMT51NdoY7FFcNNjhm0EoZVDaB1Ffy74ycIDAwuNfp8kpKFsxWMSs1CFy6IRDIIzaQc9JAIoBUBbN5rP2DcwIDAQAB"; + "ctucx.de" = + "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCg3pBG8oH8h0w5YrZ7Dpmtk+/XqE9HElWeF1SWMo86aVLkbsMKjY0WbfAq5YfEdSr/pQrILC+oAt/q6TuLzABYd7cLzK7KgdIX2SuYvujmHqzOOn1huAkzQU0wJMnMYx/0wCFMnVHXsWY9UF2zHDhYu8Jo9vuwMPwGG9u1qnfdCQIDAQAB"; + "thein.ovh" = + "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYogCqmPNomxG4KyZGsfpefFNPS5lY9aRm7TjiONKKPKQFb4oFfUBacurfL+cdGhX6CBnRr6IUXZ37e+ptOyWNFfG1e5R7dJeRdmCZvsn2DRbxCEeGA6gjl3hmRIjg3HUCWjWlzjRXV4Qke/7Q1y1lfivOrgU72bLw/V7BEi1OZQIDAQAB"; + }; + + mailAutoConfig = '' + <?xml version="1.0" encoding="UTF-8"?> + <clientConfig version="1.1"> + <emailProvider id="${primaryDomain}"> + <domain>${primaryDomain}</domain> + <displayName>${hostName}</displayName> + <displayShortName>${hostName}</displayShortName> + <incomingServer type="imap"> + <hostname>${hostName}</hostname> + <port>993</port> + <socketType>SSL</socketType> + <authentication>password-cleartext</authentication> + <username>%EMAILADDRESS%</username> + </incomingServer> + <outgoingServer type="smtp"> + <hostname>${hostName}</hostname> + <port>465</port> + <socketType>SSL</socketType> + <authentication>password-cleartext</authentication> + <username>%EMAILADDRESS%</username> + </outgoingServer> + </emailProvider> + </clientConfig> + ''; + + in + { + dns.zones = + with pkgs.dnsNix.combinators; + let + TXT = [ "v=spf1 a mx ip4:${ip4Address} +ip6:${ip6Address} ~all" ]; + DMARC = "v=DMARC1; p=none"; + MX = with mx; [ (mx 10 "${hostName}.") ]; + in + { + "${primaryDomain}" = { + inherit MX TXT; + + SRV = [ + { + proto = "tcp"; + service = "imaps"; + priority = 0; + weight = 1; + port = 993; + target = "${hostName}."; + } + { + proto = "tcp"; + service = "imap"; + priority = 0; + weight = 1; + port = 143; + target = "${hostName}."; + } + { + proto = "tcp"; + service = "submission"; + priority = 0; + weight = 1; + port = 587; + target = "${hostName}."; + } + ]; + + subdomains = { + autoconfig.CNAME = [ "${hostName}." ]; + _dmarc.TXT = [ DMARC ]; + "${config.mailserver.dkimSelector}._domainkey".TXT = [ + primaryDomainDkimKey + ]; + }; + }; + } + // (lib.mapAttrs (_domain: dkimKey: { + inherit MX TXT; + + subdomains = { + _dmarc.TXT = [ DMARC ]; + "${config.mailserver.dkimSelector}._domainkey".TXT = [ dkimKey ]; + }; + }) extraDomains); + + sops.secrets = { + "restic/mailserver/repositoryPassword" = { }; + "restic/mailserver/sshPrivateKey" = { }; + "mailPasswords/katja@zaphyra.eu" = { }; + "mailPasswords/gts@zaphyra.eu" = { }; + "mailPasswords/vaultwarden@zaphyra.eu" = { }; + "sieveScripts/katja@zaphyra.eu.sieve" = { + sopsFile = sopsSecrets.zaphyra.sieve; + key = "katja@zaphyra.eu"; + restartUnits = [ "dovecot2.service" ]; + owner = "virtualMail"; + group = "virtualMail"; + path = "/etc/dovecot/sieve/katja-zaphyra-eu.sieve"; + }; + }; + + systemd.services.dovecot.after = [ "sops-install-secrets.service" ]; + + security.acme.certs."${hostName}".reloadServices = [ + "postfix.service" + "dovecot.service" + ]; + + common = { + configure.persist.system.dirs = [ + "/var/lib/dhparams" + "/var/lib/dovecot" + "/var/lib/postfix" + { + directory = "/var/lib/dkimKeys"; + mode = "0700"; + user = "rspamd"; + group = "rspamd"; + } + { + directory = "/var/lib/mailboxes"; + mode = "0700"; + user = "virtualMail"; + group = "virtualMail"; + } + { + directory = "/var/lib/redis-rspamd"; + mode = "0700"; + user = "redis-rspamd"; + group = "redis-rspamd"; + } + { + directory = "/var/lib/rspamd"; + mode = "0700"; + user = "rspamd"; + group = "rspamd"; + } + { + directory = "/var/lib/sieve"; + mode = "0770"; + user = "virtualMail"; + group = "virtualMail"; + } + ]; + + services.resticBackup.mailserver = { + enable = true; + targets = [ "isodon.fc9f.de" ]; + sshKeyFile = config.sops.secrets."restic/mailserver/sshPrivateKey".path; + passwordFile = config.sops.secrets."restic/mailserver/repositoryPassword".path; + paths = [ + "/var/lib/mailboxes" + "/var/lib/dkimKeys" + "/var/lib/sieve" + ]; + }; + }; + + services = { + dovecot2 = { + pluginSettings.sieve_global = "/etc/dovecot/sieve"; + sieve.extensions = [ "editheader" ]; + }; + nginx.virtualHosts."autoconfig.${primaryDomain}" = { + useACMEHost = "${config.networking.fqdn}"; + forceSSL = true; + locations."= /mail/config-v1.1.xml".return = "200 '${mailAutoConfig}'"; + }; + }; + + mailserver = { + enable = true; + stateVersion = 3; + + openFirewall = true; + localDnsResolver = false; + virusScanning = false; + + certificateScheme = "acme"; + + enableManageSieve = true; + enableSubmission = true; + enableSubmissionSsl = true; + enableImap = true; + enableImapSsl = true; + enablePop3 = false; + enablePop3Ssl = false; + + fullTextSearch.enable = true; + dmarcReporting.enable = true; + + indexDir = "/var/lib/dovecot/indices"; + mailDirectory = "/var/lib/mailboxes"; + sieveDirectory = "/var/lib/sieve"; + dkimKeyDirectory = "/var/lib/dkimKeys"; + + fqdn = hostName; + systemDomain = primaryDomain; + systemName = "zaphyra-mail"; + + domains = [ primaryDomain ] ++ (lib.attrNames extraDomains); + + loginAccounts = { + "katja@zaphyra.eu" = { + hashedPasswordFile = config.sops.secrets."mailPasswords/katja@zaphyra.eu".path; + sieveScript = '' + require ["include"]; + include :global "katja-zaphyra-eu"; + ''; + aliases = [ + "@zaphyra.eu" + "@ctu.cx" + "@ctucx.de" + "@thein.ovh" + "@katja.wtf" + ]; + }; + "gts@zaphyra.eu" = { + hashedPasswordFile = config.sops.secrets."mailPasswords/gts@zaphyra.eu".path; + sendOnly = true; + }; + "vaultwarden@zaphyra.eu" = { + hashedPasswordFile = config.sops.secrets."mailPasswords/vaultwarden@zaphyra.eu".path; + sendOnly = true; + }; + }; + }; + } + ); + +}
diff --git a/nixosModules/zpha/configure/matrixBridges.nix b/nixosModules/zpha/configure/matrixBridges.nix @@ -0,0 +1,156 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.configure.matrixBridges.enable = lib.mkEnableOption ""; + + config = lib.mkIf config.zpha.configure.matrixBridges.enable { + assertions = [ + { + assertion = config.zpha.websites."continuwuity.zaphyra.eu".enable; + message = "The option 'zpha.websites.\"continuwuity.zaphyra.eu\"' must be enabled in order to use this module."; + } + ]; + + sops.secrets = { + "environments/mautrixBridges/signal" = { }; + "environments/mautrixBridges/telegram" = { }; + "environments/mautrixBridges/whatsapp" = { }; + }; + + common.configure.persist.system.dirs = [ + "/var/lib/private/mautrix-signal" + "/var/lib/private/mautrix-telegram" + "/var/lib/private/mautrix-whatsapp" + ]; + + common.services.mautrixBridge = + let + commonSettings = rec { + database.type = "sqlite3-fk-wal"; + + homeserver.address = "http://[::1]:6167"; + homeserver.domain = "zaphyra.eu"; + + encryption.allow = true; + encryption.default = true; + encryption.pickle_key = "$MAUTRIX_PICKLE_KEY"; + + relay.enabled = false; + backfill.enabled = true; + + double_puppet.secrets."${homeserver.domain}" = "as_token:$MAUTRIX_DOUBLE_PUPPET_SHARED_SECRET"; + + bridge = { + personal_filtering_spaces = true; + + permissions."${homeserver.domain}" = "admin"; + + cleanup_on_logout = { + enabled = true; + } + // (lib.genAttrs [ "manual" "bad_credentials" ] (_name: { + shared_has_users = "delete"; + shared_no_users = "delete"; + relayed = "delete"; + private = "delete"; + })); + }; + }; + in + { + signal = { + enable = true; + package = pkgs.mautrix-signal.override { withGoolm = true; }; + environmentFile = config.sops.secrets."environments/mautrixBridges/signal".path; + serviceDependencies = [ "continuwuity.service" ]; + settings = lib.recursiveUpdate commonSettings { + database.uri = "file:/var/lib/mautrix-signal/mautrix-signal.db?_txlock=immediate"; + + network = { + device_name = "Mautix-Signal (on zaphyra.eu)"; + displayname_template = "{{or .ProfileName .PhoneNumber \"Unknown user\"}} (Signal)"; + }; + + appservice = { + id = "signal"; + address = "http://[::1]:29328/"; + hostname = "[::1]"; + port = 29328; + as_token = "$MAUTRIX_AS_TOKEN"; + hs_token = "$MAUTRIX_HS_TOKEN"; + }; + }; + }; + + telegram = { + enable = true; + package = pkgs.zpha.mautrix-telegram; + environmentFile = config.sops.secrets."environments/mautrixBridges/telegram".path; + serviceDependencies = [ "continuwuity.service" ]; + settings = lib.recursiveUpdate commonSettings { + database.uri = "file:/var/lib/mautrix-telegram/mautrix-telegram.db?_txlock=immediate"; + + backfill.max_initial_messages = 400; + + appservice = { + id = "telegram"; + address = "http://[::1]:29317/"; + hostname = "[::1]"; + port = 29317; + as_token = "$MAUTRIX_AS_TOKEN"; + hs_token = "$MAUTRIX_HS_TOKEN"; + }; + + network = { + device_info.device_model = "Mautix-TelegramGO (on zaphyra.eu)"; + api_id = "$TELEGRAM_API_ID"; + api_hash = "$TELEGRAM_API_HASH"; + }; + }; + }; + + whatsapp = { + enable = true; + package = pkgs.mautrix-whatsapp.override { withGoolm = true; }; + environmentFile = config.sops.secrets."environments/mautrixBridges/whatsapp".path; + serviceDependencies = [ "continuwuity.service" ]; + settings = lib.recursiveUpdate commonSettings { + database.uri = "file:/var/lib/mautrix-whatsapp/mautrix-whatsapp.db?_txlock=immediate"; + + backfill.max_initial_messages = 100; + + appservice = { + id = "whatsapp"; + address = "http://[::1]:29318/"; + hostname = "[::1]"; + port = 29318; + as_token = "$MAUTRIX_AS_TOKEN"; + hs_token = "$MAUTRIX_HS_TOKEN"; + }; + + network = { + displayname_template = "{{or .BusinessName .PushName .Phone}} (WA)"; + os_name = "Mautrix-WhatsApp (on zaphyra.eu)"; + identity_change_notices = true; + url_previews = true; + history_sync = { + request_full_sync = true; + full_sync_config = { + days_limit = 1000; + size_mb_limit = 1024; + storage_quota_mb = 1024; + }; + }; + }; + }; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/configure/netcupTunnel.nix b/nixosModules/zpha/configure/netcupTunnel.nix @@ -0,0 +1,140 @@ +{ + nixosConfigurations, + machines, + machineConfig, + config, + lib, + ... +}: +let + inherit (lib) types; + cfg = config.zpha.configure.netcupTunnel; + +in +{ + + options.zpha.configure.netcupTunnel = { + enable = lib.mkEnableOption ""; + + addresses = lib.mkOption { + type = types.listOf types.str; + default = [ + "${machineConfig.networking.ip6Address}/${toString machineConfig.networking.ip6PrefixLength}" + ]; + }; + + wgPrivateKey = lib.mkOption { + type = types.path; + default = config.sops.secrets."wgPrivateKey".path; + }; + + wgPublicKey = lib.mkOption { + type = types.str; + default = machineConfig.wgPublicKey; + }; + + }; + + config = lib.mkIf cfg.enable { + networking.firewall = { + trustedInterfaces = [ + "wg-netcup" + "netcup" + ]; + allowedUDPPorts = [ + config.systemd.network.netdevs."15-wg-netcup".wireguardConfig.ListenPort + ]; + }; + + systemd.network = { + config.networkConfig = { + IPv6Forwarding = true; + }; + + netdevs = { + "10-netcup" = lib.mkIf (config.networking.hostName != "sorrah") { + netdevConfig = { + Kind = "dummy"; + Name = "netcup"; + }; + }; + "15-wg-netcup" = { + netdevConfig = { + Kind = "wireguard"; + Name = "wg-netcup"; + }; + + wireguardConfig = { + PrivateKeyFile = cfg.wgPrivateKey; + ListenPort = 51820; + FirewallMark = 51820; + }; + + wireguardPeers = + if (config.networking.hostName == "sorrah") then + (lib.pipe nixosConfigurations [ + (lib.filterAttrs (name: _: name != config.networking.hostName)) + (lib.filterAttrs (_: value: value.config.zpha.configure.netcupTunnel.enable)) + (lib.mapAttrsToList ( + name: value: { + PublicKey = value.config.zpha.configure.netcupTunnel.wgPublicKey; + AllowedIPs = value.config.zpha.configure.netcupTunnel.addresses; + PersistentKeepalive = 10; + } + )) + ]) + else + [ + { + PublicKey = machines.sorrah.wgPublicKey; + Endpoint = "${machines.sorrah.networking.ip4Address}:51820"; + AllowedIPs = [ "::/0" ]; + PersistentKeepalive = 10; + } + ]; + }; + }; + + networks = { + "5-primaryInterface" = lib.mkIf (config.networking.hostName == "sorrah") { + networkConfig = { + IPv6ProxyNDP = true; + IPv6ProxyNDPAddress = lib.pipe config.systemd.network.netdevs."15-wg-netcup".wireguardPeers [ + (map (element: if element ? AllowedIPs then element.AllowedIPs else [ ])) + lib.flatten + (map (element: builtins.elemAt (lib.strings.splitString "/" element) 0)) + ]; + }; + }; + "10-netcup" = lib.mkIf (config.networking.hostName != "sorrah") { + enable = true; + name = "netcup"; + address = cfg.addresses; + routingPolicyRules = lib.singleton { + From = "2a03:4000:4d:5e:acab::/112"; + Table = 1234; + Priority = 2000; + }; + }; + "15-wg-netcup" = { + matchConfig.Name = "wg-netcup"; + linkConfig.RequiredForOnline = false; + } + // ( + if (config.networking.hostName == "sorrah") then + { + address = cfg.addresses; + } + else + { + routes = lib.singleton { + Destination = "::/0"; + Table = "1234"; + }; + } + ); + }; + }; + }; + +}
diff --git a/nixosModules/zpha/configure/networkManagerProfiles.nix b/nixosModules/zpha/configure/networkManagerProfiles.nix @@ -0,0 +1,74 @@ +{ + sopsSecrets, + config, + lib, + ... +}: + +{ + + options.zpha.configure.networkManagerProfiles.enable = lib.mkEnableOption "NetworkManager profile"; + + config = lib.mkIf config.zpha.configure.networkManagerProfiles.enable { + sops.secrets = { + "environments/networkManagerProfiles/zaphyraPhone".sopsFile = sopsSecrets.common; + "environments/networkManagerProfiles/grogHome".sopsFile = sopsSecrets.common; + }; + + networking.networkmanager.ensureProfiles = { + environmentFiles = [ + config.sops.secrets."environments/networkManagerProfiles/zaphyraPhone".path + config.sops.secrets."environments/networkManagerProfiles/grogHome".path + ]; + profiles = { + "grogHome" = { + connection = { + id = "grogHome"; + type = "wifi"; + uuid = "1bbc0cce-148f-4afa-876e-fa6db67d884e"; + }; + ipv4 = { + method = "auto"; + }; + ipv6 = { + addr-gen-mode = "default"; + method = "auto"; + }; + proxy = { }; + wifi = { + mode = "infrastructure"; + ssid = "$GROG_HOME_SSID"; + }; + wifi-security = { + key-mgmt = "wpa-psk"; + psk = "$GROG_HOME_PASS"; + }; + }; + "zaphyraPhone" = { + connection = { + id = "zaphyraPhone"; + type = "wifi"; + uuid = "2dc7bb4a-e016-427d-aeb6-28e7a6b91f41"; + }; + ipv4 = { + method = "auto"; + }; + ipv6 = { + addr-gen-mode = "default"; + method = "auto"; + }; + proxy = { }; + wifi = { + mode = "infrastructure"; + ssid = "$ZAPHYRA_PHONE_SSID"; + }; + wifi-security = { + auth-alg = "open"; + key-mgmt = "wpa-psk"; + psk = "$ZAPHYRA_PHONE_PASS"; + }; + }; + }; + }; + }; +}
diff --git a/nixosModules/zpha/configure/syncthing.nix b/nixosModules/zpha/configure/syncthing.nix @@ -0,0 +1,226 @@ +{ + nixosConfigurations, + machineConfig, + config, + lib, + ... +}: +let + inherit (lib) types; + cfg = config.zpha.configure.syncthing; + +in +{ + options.zpha.configure.syncthing = { + enable = lib.mkEnableOption ""; + + user = lib.mkOption { + type = types.str; + default = "zaphyra"; + }; + + group = lib.mkOption { + type = types.str; + default = "users"; + }; + + dataDir = lib.mkOption { + type = types.str; + default = "/persist/home/zaphyra"; + }; + + configDir = lib.mkOption { + type = types.str; + default = "/persist/home/zaphyra/.config/syncthing"; + }; + + id = lib.mkOption { + type = types.str; + default = machineConfig.syncthingId; + }; + + enabledShares = lib.mkOption { + type = with types; listOf str; + default = [ + "zaphyra-audiobooks" + "zaphyra-documents" + "zaphyra-pictures" + "zaphyra-music" + "zaphyra-music-orig" + "zaphyra-videos" + "zaphyra-db-richtlinien" + ]; + }; + }; + + config = lib.mkIf cfg.enable ( + let + syncthingDevices = { + #mobile devices + huntii.id = "6YZT5PZ-EKXZBMV-C2MJL75-OCQ36LC-L3QIRPW-VJ5EU3C-2ICZDDO-IZ7IGAO"; + haueri.id = "YMIK4FX-2IVSTHP-CRZHAEM-2KNSZTP-QR5JTSD-MSSR3XV-SUCJF4F-T5SSDAN"; + iphone.id = "3SM3LJV-XMHYW2D-MU5WQ3T-KGYUJOI-LXOL6YI-BSVZ2B5-QJ6GVXN-MPWMKQ7"; + + #servers + isodon.name = "isodon.fc9f.de"; + isodon.id = "QI2EPUE-4VMZ3XV-LXX3GXP-RHCWTRY-AACLSGL-YG7MIYV-THST74N-KJGIBQ6"; + + cuvier.name = "cuvier.fc9f.de"; + cuvier.id = "JUIJQZE-AWVYZIZ-CR6E66M-TAJIRDH-NEPEPZW-FHO37MJ-QM2MV5Q-OFJTEQI"; + + #others + #celestine.id = "K6DSDMU-PV56YYN-3XNIU2K-DKIFYT5-AL3PWNS-73M3V52-CYO5WLP-B655VAG"; + }; + + defaultVersioning = { + type = "trashcan"; + params = { + cleanoutDays = "3"; + }; + }; + + isCurrentHost = key: _value: key != config.networking.hostName; + + deviceNames = lib.pipe syncthingDevices [ + (lib.filterAttrs isCurrentHost) + builtins.attrNames + ]; + + shareDeviceNames = + share: + (lib.pipe syncthingDevices [ + (lib.filterAttrs isCurrentHost) + (lib.filterAttrs ( + name: _value: + let + syncthingCfg = nixosConfigurations."${name}".config.modules.presets.zaphyra.syncthing; + in + if (nixosConfigurations ? name) then + if syncthingCfg.enable then lib.elem share syncthingCfg.enabledShares else false + else + false + )) + builtins.attrNames + ]); + + devices = lib.pipe syncthingDevices [ + (lib.filterAttrs isCurrentHost) + ]; + + folders = + let + inherit (config.services.syncthing) dataDir; + + in + { + zaphyra-audiobooks = rec { + enable = lib.mkDefault (lib.elem id cfg.enabledShares); + id = "zaphyra-audiobooks"; + label = "Audiobooks"; + path = lib.mkDefault "${dataDir}/Audiobooks"; + devices = deviceNames; + versioning = lib.mkDefault defaultVersioning; + }; + + zaphyra-documents = rec { + enable = lib.mkDefault (lib.elem id cfg.enabledShares); + id = "zaphyra-documents"; + label = "Documents"; + path = lib.mkDefault "${dataDir}/Documents"; + devices = deviceNames; + versioning = lib.mkDefault defaultVersioning; + }; + + zaphyra-music = rec { + enable = lib.mkDefault (lib.elem id cfg.enabledShares); + id = "zaphyra-music"; + label = "Music"; + path = lib.mkDefault "${dataDir}/Music"; + devices = deviceNames; + versioning = lib.mkDefault defaultVersioning; + }; + + zaphyra-pictures = rec { + enable = lib.mkDefault (lib.elem id cfg.enabledShares); + id = "zaphyra-pictures"; + label = "Pictures"; + path = lib.mkDefault "${dataDir}/Pictures"; + devices = deviceNames; + versioning = lib.mkDefault defaultVersioning; + }; + + zaphyra-videos = rec { + enable = lib.mkDefault (lib.elem id cfg.enabledShares); + id = "zaphyra-videos"; + label = "Videos"; + path = lib.mkDefault "${dataDir}/Videos"; + devices = deviceNames; + versioning = lib.mkDefault defaultVersioning; + }; + + zaphyra-db-richtlinien = rec { + enable = lib.mkDefault (lib.elem id cfg.enabledShares); + id = "zaphyra-db-richtlinien"; + label = "Bahn-Richtlinien"; + path = lib.mkDefault "${dataDir}/Bahn-Richtlinien"; + devices = deviceNames; + versioning = lib.mkDefault defaultVersioning; + }; + + zaphyra-music-orig = rec { + enable = lib.mkDefault (lib.elem id cfg.enabledShares); + id = "zaphyra-music-orig"; + label = "Music (Originals)"; + path = lib.mkDefault "${dataDir}/Music (Originals)"; + devices = shareDeviceNames "${id}"; + versioning = lib.mkDefault defaultVersioning; + }; + + zaphyra-media = rec { + enable = lib.mkDefault (lib.elem id cfg.enabledShares); + id = "zaphyra-media"; + label = "Media (legacy)"; + path = lib.mkDefault "${dataDir}/Media (legacy)"; + devices = shareDeviceNames "${id}"; + versioning = lib.mkDefault defaultVersioning; + }; + + zaphyra-music-inbox = rec { + enable = lib.mkDefault (lib.elem id cfg.enabledShares); + id = "zaphyra-music-inbox"; + label = "Music (Inbox)"; + path = lib.mkDefault "${dataDir}/Music (Inbox)"; + devices = (shareDeviceNames "${id}"); + versioning = lib.mkDefault defaultVersioning; + }; + + }; + + in + { + sops.secrets = lib.genAttrs [ "syncthing/cert" "syncthing/key" ] (_name: { + owner = cfg.user; + inherit (cfg) group; + }); + + services.syncthing = { + enable = true; + openDefaultPorts = true; + guiAddress = "[::1]:8384"; + + inherit (cfg) user; + inherit (cfg) group; + + cert = config.sops.secrets."syncthing/cert".path; + key = config.sops.secrets."syncthing/key".path; + + inherit (cfg) dataDir; + inherit (cfg) configDir; + + settings.devices = devices; + settings.folders = folders; + }; + } + ); + +}
diff --git a/nixosModules/zpha/configure/syncthingBackup.nix b/nixosModules/zpha/configure/syncthingBackup.nix @@ -0,0 +1,48 @@ +{ + config, + lib, + ... +}: + +{ + + options.zpha.configure.syncthingBackup.enable = lib.mkEnableOption ""; + + config = lib.mkIf config.zpha.configure.syncthingBackup.enable ( + let + shares = lib.removeAttrs config.services.syncthing.settings.folders [ "zaphyra-music" ]; + in + { + sops.secrets = lib.pipe shares [ + (lib.filterAttrs (name: value: value.enable)) + (lib.mapAttrsToList (name: value: value.id)) + (map (element: [ + (lib.nameValuePair "restic/syncthing/${element}/repositoryPassword" { }) + (lib.nameValuePair "restic/syncthing/${element}/sshPrivateKey" { }) + ])) + (lib.flatten) + (builtins.listToAttrs) + ]; + + common.services.resticBackup = lib.pipe shares [ + (lib.filterAttrs (name: value: value.enable)) + (lib.mapAttrs' ( + name: value: + lib.nameValuePair "syncthing-${name}" { + inherit (config.services.syncthing) user; + enable = true; + targets = [ "restic-target.fc9f.de" ]; + sshKeyFile = config.sops.secrets."restic/syncthing/${name}/sshPrivateKey".path; + passwordFile = config.sops.secrets."restic/syncthing/${name}/repositoryPassword".path; + paths = [ value.path ]; + exclude = [ + ".stfolder" + ".stversions" + ]; + } + )) + ]; + } + ); + +}
diff --git a/nixosModules/zpha/configure/xmppServer.nix b/nixosModules/zpha/configure/xmppServer.nix @@ -0,0 +1,38 @@ +{ + config, + lib, + ... +}: + +{ + + options.zpha.configure.xmppServer.enable = lib.mkEnableOption ""; + + config = lib.mkIf config.zpha.configure.xmppServer.enable { + sops.secrets = { + "restic/prosody/repositoryPassword" = { }; + "restic/prosody/sshPrivateKey" = { }; + }; + + common.services = { + resticBackup.prosody = { + inherit (config.services.prosody) user; + enable = true; + targets = [ + "restic-target.fc9f.de" + "isodon.fc9f.de" + ]; + sshKeyFile = config.sops.secrets."restic/prosody/sshPrivateKey".path; + passwordFile = config.sops.secrets."restic/prosody/repositoryPassword".path; + paths = [ config.services.prosody.dataDir ]; + }; + + prosody = { + enable = true; + domain = "zaphyra.eu"; + adminUsers = [ "katja@zaphyra.eu" ]; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/profiles/dn42.nix b/nixosModules/zpha/profiles/dn42.nix @@ -0,0 +1,180 @@ +{ + nixosConfigurations, + machines, + machineConfig, + config, + lib, + ... +}: +let + inherit (lib) types; + cfg = config.zpha.profiles.dn42; + +in +{ + + options.zpha.profiles.dn42 = { + enable = lib.mkEnableOption ""; + + addresses = lib.mkOption { + type = types.listOf types.str; + default = [ + "${machineConfig.networking.dn42.ip6Address}/${toString machineConfig.networking.dn42.ip6PrefixLength}" + ]; + }; + + wgPrivateKey = lib.mkOption { + type = types.path; + default = config.sops.secrets."wgPrivateKey".path; + }; + + wgPublicKey = lib.mkOption { + type = types.str; + default = machineConfig.wgPublicKey; + }; + + }; + + config = lib.mkIf cfg.enable { + dns.zones."zaphyra.dn42".subdomains."${config.networking.hostName}".AAAA = [ + ((lib.network.ipv6.fromString (lib.elemAt cfg.addresses 0)).address) + ]; + + security.acme.certs."${config.networking.hostName}.zaphyra.dn42" = { + server = "https://acme.burble.dn42/v1/dn42/acme/directory"; + validMinDays = 20; + keyType = "ec384"; + dnsProvider = null; + }; + + services.nginx.virtualHosts."${config.networking.hostName}.zaphyra.dn42" = { + enableACME = true; + forceSSL = true; + kTLS = true; + }; + + services.resolved = { + enable = true; + fallbackDns = [ + "8.8.8.8" + "2001:4860:4860::8844" + ]; + }; + + systemd.network = { + netdevs."20-dn42" = { + netdevConfig = { + Kind = "wireguard"; + Name = "dn42"; + MTUBytes = 1280; + }; + + wireguardConfig = { + PrivateKeyFile = cfg.wgPrivateKey; + ListenPort = 1718; + FirewallMark = 1718; + }; + + wireguardPeers = + if config.zpha.configure.dn42Router.enable then + (lib.pipe nixosConfigurations [ + (lib.filterAttrs (name: _: name != config.networking.hostName)) + (lib.filterAttrs (_: value: value.config.zpha.profiles.dn42.enable)) + (lib.mapAttrsToList ( + name: value: { + PublicKey = value.config.zpha.profiles.dn42.wgPublicKey; + AllowedIPs = value.config.zpha.profiles.dn42.addresses; + PersistentKeepalive = 10; + } + )) + ]) + ++ [ + { + # zaphyraThinkPad + PublicKey = "7drlp9TmHgSgqSR1PynfAzf8BIH4LWVuFDtPqGs88EY="; + AllowedIPs = [ "fd6b:6174:6a61::20/128" ]; + PersistentKeepalive = 10; + } + { + # zaphyraApplePhone + PublicKey = "3rp8iD+Nk9DsyM/JCvrV7bBnEzioG30SDqOQhNWwsVs="; + AllowedIPs = [ "fd6b:6174:6a61::21/128" ]; + PersistentKeepalive = 10; + } + { + # zaphyraPixel + PublicKey = "ski1Uya2PSCZsrBblcgoM9WL5h+1KAd61uZD2sfRDjE="; + AllowedIPs = [ "fd6b:6174:6a61::22/128" ]; + PersistentKeepalive = 10; + } + { + # zaphyraFramework + PublicKey = "YdseqpjpKGV7JWWDEJOAtqB3tzk7vI/gPFiqmCyeVTM="; + AllowedIPs = [ "fd6b:6174:6a61::23/128" ]; + PersistentKeepalive = 10; + } + ] + else + [ + { + PublicKey = machines.sorrah.networking.dn42.wgPublicKey; + Endpoint = "[${machines.sorrah.networking.ip6Address}]:1718"; + AllowedIPs = [ "fd00::/8" ]; + PersistentKeepalive = 10; + } + ]; + }; + + networks."20-dn42" = { + matchConfig.Name = "dn42"; + linkConfig.RequiredForOnline = false; + address = cfg.addresses; + } + // ( + if config.zpha.configure.dn42Router.enable then + { } + else + { + routes = [ { Destination = "fd00::/8"; } ]; + networkConfig = { + DNSDefaultRoute = false; + DNS = [ "fd6b:6174:6a61::1" ]; + Domains = [ + "~dn42" + "d.f.ip6.arpa" + ]; + }; + } + ); + }; + + #dn42 root ca + security.pki.certificates = lib.singleton '' + -----BEGIN CERTIFICATE----- + MIID8DCCAtigAwIBAgIFIBYBAAAwDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMC + WEQxDTALBgNVBAoMBGRuNDIxIzAhBgNVBAsMGmRuNDIgQ2VydGlmaWNhdGUgQXV0 + aG9yaXR5MR8wHQYDVQQDDBZkbjQyIFJvb3QgQXV0aG9yaXR5IENBMCAXDTE2MDEx + NjAwMTIwNFoYDzIwMzAxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJYRDENMAsGA1UE + CgwEZG40MjEjMCEGA1UECwwaZG40MiBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAd + BgNVBAMMFmRuNDIgUm9vdCBBdXRob3JpdHkgQ0EwggEiMA0GCSqGSIb3DQEBAQUA + A4IBDwAwggEKAoIBAQDBGRDeAYYR8YIMsNTl/5rI46r0AAiCwM9/BXohl8G1i6PR + VO76BA931VyYS9mIGMEXEJLlJPrvYetdexHlvrqJ8mDJO4IFOnRUYCNmGtjNKHvx + 6lUlmowEoP+dSFRMnbwtoN9xrmRHDed1BfTFAirSDL6jY1RiK60p62oIpF6o6/FS + FE7RXUEv0xm65II2etGj8oT2B7L2DDDb23bu6RQFx491tz/V1TVW0JJE3yYeAPqu + y3rJUGddafj5/SWnHdtAsUK8RVfhyRxCummAHuolmRKfbyOj0i5KzRXkfEn50cDw + GQwVUM6mUbuqFrKC7PRhRIwc3WVgBHewTZlnF/sJAgMBAAGjgaowgacwDgYDVR0P + AQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFR2iLLAtTDQ/E/J + bTv5jFURrBUVMB8GA1UdIwQYMBaAFFR2iLLAtTDQ/E/JbTv5jFURrBUVMEQGA1Ud + HgQ9MDugOTAHggUuZG40MjAKhwisFAAA//wAADAihyD9QgAAAAAAAAAAAAAAAAAA + //8AAAAAAAAAAAAAAAAAADANBgkqhkiG9w0BAQsFAAOCAQEAXKQ7QaCBaeJxmU11 + S1ogDSrZ7Oq8jU+wbPMuQRqgdfPefjrgp7nbzfUW5GrL58wqj+5/FAqltflmSIHl + aB4MpqM8pyvjlc/jYxUNFglj2WYxO0IufBrlKI5ePZ4omUjpR4YR4gQpYCuWlZmu + P6v/P0WrfgdFTk0LGEA9OwKcTqkPpcI/SjB3rmZcs42yQWvimAF94GtScE09uKlI + 9QLS2UBmtl5EJRFVrDEC12dyamq8dDRfddyaT4MoQOAq3D9BQ1pHByu3pz/QFaJC + 1zAi8vbktPY7OMprTOc8pHDL3q8KFP8jJcoEzZ5Jw0vkCrULhLXvtFtjB0djzVxQ + C0IKqQ== + -----END CERTIFICATE----- + ''; + }; + +}
diff --git a/nixosModules/zpha/profiles/graphical.nix b/nixosModules/zpha/profiles/graphical.nix @@ -0,0 +1,136 @@ +{ + config, + lib, + pkgs, + ... +}: +{ + + options.zpha.profiles.graphical.enable = lib.mkEnableOption "graphical preset"; + + config = lib.mkIf config.zpha.profiles.graphical.enable { + preservation.preserveAt."/persist".users.zaphyra = { + commonMountOptions = [ + "x-gdu.hide" + "x-gvfs-hide" + ]; + + directories = [ + "Downloads" + "Documents" + "Pictures" + "Videos" + "Music" + + "Audiobooks" + "Bahn-Richtlinien" + + "proj" + + { + directory = ".local/share/Trash"; + # Trash should be accessed via a symlink. Nautilus is incompatible + # with a bind-mounted trash. + how = "symlink"; + } + ]; + }; + + services.udisks2.enable = true; + systemd.network.wait-online.enable = false; + + common = { + configure = { + fonts.enable = true; + }; + + hardware = { + intelGraphics.enable = true; + bluetooth.enable = true; + audio.enable = true; + smartcard.enable = true; + }; + + services = { + upower.enable = true; + power-profiles-daemon.enable = true; + + NetworkManager.enable = true; + + gvfs.enable = true; + + greetd = { + enable = true; + autoLogin.command = "niri-session -l"; + }; + }; + }; + + zpha = { + configure = { + fonts.enable = true; + networkManagerProfiles.enable = true; + }; + + services = { + dssd.enable = true; + avahi.enable = true; + pipewire.enable = true; + batsignal.enable = true; + + oniri.enable = true; + }; + + programs = { + shellUtilities.enable = true; + nixUtilities.enable = true; + deploymentUtilities.enable = true; + chaosctl.enable = true; + + broot.enable = true; + micro.enable = true; + + git.enable = true; + gpg.enable = true; + + niri.enable = true; + swaylock.enable = true; + vibepanel.enable = true; + vicinae.enable = true; + + wpaperd.enable = true; + shaderbg.enable = true; + + ghostty.enable = true; + + nautilus.enable = true; + gnome-text-editor.enable = true; + celluloid.enable = true; + loupe.enable = true; + papers.enable = true; + pdfarranger.enable = true; + yt-dlp.enable = true; + typst.enable = true; + + firefox.enable = true; + thunderbird.enable = true; + gajim.enable = true; + dino.enable = true; + gnome-calendar.enable = true; + }; + }; + + users.users.zaphyra = { + extraGroups = [ "networkmanager" ]; + maid = { + xdg.enable = true; + + packages = with pkgs; [ + adw-bluetooth + zpha.nirilayout + ]; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/profiles/nginx.nix b/nixosModules/zpha/profiles/nginx.nix @@ -0,0 +1,75 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.profiles.nginx.enable = lib.mkEnableOption "nginx"; + + config = lib.mkIf config.zpha.profiles.nginx.enable { + common.configure.persist.system.dirs = [ "/var/lib/acme" ]; + + sops.secrets.acmeTSIGKey = { }; + + security.acme = { + acceptTerms = true; + defaults = { + email = "letsencrypt@zaphyra.eu"; + keyType = "ec384"; + dnsProvider = "rfc2136"; + environmentFile = pkgs.writeText "acme-dns-env" '' + RFC2136_NAMESERVER=ns1.fc9f.de + RFC2136_TSIG_KEY=acme-nix-${config.networking.hostName} + RFC2136_TSIG_ALGORITHM=hmac-sha384 + ''; + credentialFiles = { + RFC2136_TSIG_SECRET_FILE = config.sops.secrets.acmeTSIGKey.path; + }; + }; + certs."${config.networking.fqdn}" = { + group = lib.mkIf config.services.nginx.enable "nginx"; + extraDomainNames = lib.pipe config.services.nginx.virtualHosts [ + (lib.mapAttrsToList ( + key: config: [ + (config.serverAliases or [ ]) + key + ] + )) + lib.flatten + (lib.remove config.networking.fqdn) + (lib.filter (domain: !(lib.hasSuffix "dn42" domain))) + ]; + }; + }; + + services.nginx = { + enable = lib.mkDefault true; + recommendedGzipSettings = true; + recommendedOptimisation = true; + recommendedProxySettings = true; + recommendedTlsSettings = true; + commonHttpConfig = '' + server_names_hash_bucket_size 64; + charset utf-8; + + access_log off; + ''; + + virtualHosts."${config.networking.fqdn}" = { + useACMEHost = "${config.networking.fqdn}"; + forceSSL = true; + kTLS = true; + default = true; + }; + }; + + networking.firewall.allowedTCPPorts = [ + 80 + 443 + ]; + }; + +}
diff --git a/nixosModules/zpha/profiles/resticBackupTarget.nix b/nixosModules/zpha/profiles/resticBackupTarget.nix @@ -0,0 +1,53 @@ +{ + config, + lib, + pkgs, + ... +}: +let + inherit (lib) types; + cfg = config.zpha.profiles.resticBackupTarget; + +in +{ + + options.zpha.profiles.resticBackupTarget = { + enable = lib.mkEnableOption "this machine to be used as restic backup target"; + path = lib.mkOption { + type = types.path; + }; + keys = lib.mkOption { + type = with types; attrsOf str; + default = { }; + }; + }; + + config = lib.mkIf cfg.enable { + dns.zones."fc9f.de".subdomains."restic-target".CNAME = lib.mkIf ( + config.networking.hostName == "cuvier" + ) [ "${config.networking.fqdn}." ]; + + users = { + groups.restic = { }; + users.restic = { + uid = 1002; + isNormalUser = true; + group = "restic"; + extraGroups = [ + "ssh" + "nix" + ]; + home = cfg.path; + maid.packages = with pkgs; [ + restic + rclone + ]; + openssh.authorizedKeys.keys = lib.mapAttrsToList ( + repo: key: + ''restrict,command="${lib.getExe pkgs.rclone} serve restic --stdio --append-only --verbose ${cfg.path}/${repo}" ${key}'' + ) cfg.keys; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/profiles/zaphyra.nix b/nixosModules/zpha/profiles/zaphyra.nix @@ -0,0 +1,103 @@ +{ + npins, + pkgs, + lib, + config, + machineConfig, + ... +}: + +let + cfg = config.zpha.profiles.zaphyra; + +in +{ + + options.zpha.profiles.zaphyra.enable = lib.mkEnableOption "zaphyra's defaults"; + + imports = + (lib.mkIf cfg.enable [ + "${npins.nixMaid}/src/nixos" + ]).content; + + config = lib.mkIf cfg.enable { + dns.zones."fc9f.de".subdomains."${config.networking.hostName}" = + lib.mkIf (machineConfig ? networking) + ( + let + networkCfg = machineConfig.networking; + in + { + AAAA = lib.mkIf ((networkCfg ? ip6Address) && !networkCfg.ip6IsPrivate) [ + networkCfg.ip6Address + ]; + A = lib.mkIf ((networkCfg ? ip4Address) && !networkCfg.ip4IsPrivate) [ + networkCfg.ip4Address + ]; + } + ); + + maid.sharedModules = [ + ../../../maidModules/environment.nix + ../../../maidModules/xdg.nix + ../../../maidModules/dbus.nix + ../../../maidModules/programs/fish.nix + ../../../maidModules/programs/starship.nix + ../../../maidModules/programs/git.nix + ../../../maidModules/programs/lazygit.nix + ]; + + users.users = { + root = { + extraGroups = [ "ssh" ]; + openssh.authorizedKeys.keys = [ + (builtins.readFile "${pkgs.zpha.website}/ssh_pubkey.asc") + ]; + }; + zaphyra.maid = { + programs = { + starship.enable = true; + }; + }; + }; + + zpha = { + profiles.nginx.enable = true; + programs = { + fish.enable = true; + htop.enable = true; + ssh.enable = true; + }; + }; + + common = { + configure = { + rootDisk.enable = lib.mkDefault true; + persist.system.enable = lib.mkDefault true; + persist.home.enable = lib.mkDefault true; + }; + + profiles = { + base.enable = true; + minimal.enable = lib.mkDefault true; + }; + + services.vnstat.enable = true; + + users.zaphyra.enable = true; + }; + + services = { + timesyncd.enable = lib.mkDefault true; + fstrim.enable = lib.mkDefault true; + + speechd.enable = false; + + journald.extraConfig = "SystemMaxUse=2.5G"; + + # logind.settings.Login.KillUserProcesses = lib.mkDefault true; + nginx.appendHttpConfig = ''add_header X-Clacks-Overhead "GNU memdmp" always;''; + }; + }; + +}
diff --git a/nixosModules/zpha/programs/broot.nix b/nixosModules/zpha/programs/broot.nix @@ -0,0 +1,54 @@ +{ + lib, + pkgs, + config, + ... +}: + +{ + + options.zpha.programs.broot.enable = lib.mkEnableOption "broot"; + + config = lib.mkIf config.zpha.programs.broot.enable { + users.users.zaphyra.maid = { + packages = [ pkgs.broot ]; + + file.xdg_config = { + "broot/conf.hjson".text = builtins.toJSON { + verbs = [ + { + invocation = "edit"; + shortcut = "e"; + key = "enter"; + apply_to = "text_file"; + external = "$EDITOR {file:space-separated}"; + leave_broot = false; + } + { + invocation = "create {subpath}"; + execution = "$EDITOR {directory}/{subpath}"; + leave_broot = false; + } + { + invocation = "git_diff"; + shortcut = "gd"; + leave_broot = false; + execution = "git difftool -y {file}"; + } + { + key = "alt-pagedown"; + internal = "page_down"; + impacted_panel = "right"; + } + { + key = "alt-pageup"; + internal = "page_up"; + impacted_panel = "right"; + } + ]; + }; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/programs/celluloid.nix b/nixosModules/zpha/programs/celluloid.nix @@ -0,0 +1,25 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.programs.celluloid.enable = lib.mkEnableOption "celluloid"; + + config = lib.mkIf config.zpha.programs.celluloid.enable { + users.users.zaphyra.maid = { + packages = [ pkgs.celluloid ]; + gsettings.settings = { + io.github.celluloid-player.celluloid = { + always-append-to-playlist = true; + draggable-video-area-enable = true; + always-autohide-cursor = true; + }; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/programs/chaosctl.nix b/nixosModules/zpha/programs/chaosctl.nix @@ -0,0 +1,27 @@ +{ + config, + pkgs, + lib, + ... +}: + +{ + + options.zpha.programs.chaosctl.enable = lib.mkEnableOption "chaos-darmstadt related stuff"; + + config = lib.mkIf config.zpha.programs.chaosctl.enable { + users.users.zaphyra.maid.packages = [ + pkgs.zpha.chaosctl + (pkgs.writeShellScriptBin "cda-door-buzzer" '' + export GNUPGHOME=/home/zaphyra/.config/gnupg + export SSH_AUTH_SOCK=$(${lib.getExe' pkgs.gnupg "gpgconf"} --list-dirs agent-ssh-socket) + ${lib.getExe' pkgs.libnotify "notify-send"} -e "CCCDA-Door" "$(${lib.getExe pkgs.zpha.chaosctl} door buzzer)" + '') + ]; + + zpha.programs.niri.settings.binds = { + "Mod+Shift+D".spawn = lib.singleton "cda-door-buzzer"; + }; + }; + +}
diff --git a/nixosModules/zpha/programs/deploymentUtilities.nix b/nixosModules/zpha/programs/deploymentUtilities.nix @@ -0,0 +1,24 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.programs.deploymentUtilities.enable = lib.mkEnableOption "deployment utilities"; + + config = lib.mkIf config.zpha.programs.deploymentUtilities.enable { + users.users.zaphyra.maid.packages = with pkgs; [ + age + ssh-to-age + + sops + + #deploy-rs + nh + ]; + }; + +}
diff --git a/nixosModules/zpha/programs/dino.nix b/nixosModules/zpha/programs/dino.nix @@ -0,0 +1,22 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.programs.dino.enable = lib.mkEnableOption "gajim"; + + config = lib.mkIf config.zpha.programs.dino.enable { + preservation.preserveAt."/persist".users.zaphyra.directories = [ + ".local/share/dino" + ]; + + users.users.zaphyra.maid = { + packages = [ pkgs.dino ]; + }; + }; + +}
diff --git a/nixosModules/zpha/programs/firefox.nix b/nixosModules/zpha/programs/firefox.nix @@ -0,0 +1,385 @@ +{ + config, + lib, + pkgs, + npins, + ... +}: + +{ + + options.zpha.programs.firefox.enable = lib.mkEnableOption "firefox"; + + config = lib.mkIf config.zpha.programs.firefox.enable { + preservation.preserveAt."/persist".users.zaphyra.directories = [ + ".config/mozilla/firefox" + ".cache/mozilla/firefox" + ]; + + users.users.zaphyra.maid = { + environment.sessionVariables.BROWSER = "firefox"; + + file.xdg_config = { + "mozilla/firefox/gnome-theme".source = npins.firefoxGnomeTheme; + "mozilla/firefox/zaphyra/chrome/userChrome.css".text = '' + @import "../../gnome-theme/theme/gnome-theme.css"; + ''; + "mozilla/firefox/zaphyra/chrome/userContent.css".text = '' + @import "../../gnome-theme/userContent.css"; + ''; + "mozilla/firefox/profiles.ini".text = '' + [Profile0] + Name=zaphyra + IsRelative=1 + Path=zaphyra + Default=1 + + [General] + StartWithLastProfile=1 + Version=2 + ''; + }; + + xdg.mime-apps.defaultApplications = { + "x-scheme-handler/http" = "firefox.desktop"; + "x-scheme-handler/https" = "firefox.desktop"; + "x-scheme-handler/chrome" = "firefox.desktop"; + + "image/svg+xml" = "firefox.desktop"; + + "text/html" = "firefox.desktop"; + "text/xml" = "firefox.desktop"; + }; + + packages = lib.singleton ( + pkgs.firefox.override (old: { + extraPrefsFiles = [ + (pkgs.writeText "firefox-autoconfig.js" ( + let + uiState = { + currentVersion = 23; + newElementCount = 0; + seen = [ ]; + dirtyAreaCache = [ ]; + placements = { + widget-overflow-fixed-list = [ ]; + toolbar-menubar = [ "menubar-items" ]; + vertical-tabs = [ + "tabbrowser-tabs" + ]; + PersonalToolbar = [ ]; + unified-extensions-area = [ + "containerise_kinte_sh-browser-action" + "_e9090647-32ff-48e4-9c3c-1361e8fd270e_-browser-action" + "_contain-facebook-browser-action" + "_12cf650b-1822-40aa-bff0-996df6948878_-browser-action" + "_testpilot-containers-browser-action" + "sponsorblocker_ajay_app-browser-action" + "_ublacklist-browser-action" + ]; + nav-bar = [ + "sidebar-button" + "back-button" + "forward-button" + "stop-reload-button" + "urlbar-container" + "downloads-button" + "_446900e4-71c2-419f-a6a7-df9c091e268b_-browser-action" + "ublock0_raymondhill_net-browser-action" + "unified-extensions-button" + ]; + TabsToolbar = [ ]; + }; + }; + in + '' + // required for firefox-gnome-theme + lockPref("browser.toolbars.bookmarks.visibility", "never"); + lockPref("browser.tabs.drawInTitlebar", true); + lockPref("browser.theme.dark-private-windows", false); + lockPref("browser.uidensity", 0); + lockPref("svg.context-properties.content.enabled", true); + lockPref("toolkit.legacyUserProfileCustomizations.stylesheets", true); + + // enable vertical tabs + lockPref("sidebar.verticalTabs", true); + lockPref("sidebar.visibility", "expand-on-hover"); + lockPref("sidebar.main.tools", ""); + lockPref("sidebar.verticalTabs.dragToPinPromo.dismissed", true); + + pref("browser.uiCustomization.state", "${lib.escape [ "\"" ] (builtins.toJSON uiState)}"); + '' + )) + ]; + + nativeMessagingHosts = [ ]; + + extraPolicies = { + SearchEngines = { + Default = "Kagi"; + Add = [ + { + Name = "Kagi"; + URLTemplate = "https://kagi.com/search?q={searchTerms}"; + Method = "GET"; + IconURL = "https://kagi.com/favicon.png"; + Alias = "kagi"; + Description = "Search with kagi"; + SuggestURLTemplate = "https://kagisuggest.com/api/autosuggest?q={searchTerms}"; + } + + { + Name = "Noogle"; + URLTemplate = "https://noogle.dev/q?term={searchTerms}"; + Method = "GET"; + IconURL = "https://noogle.dev/favicon.png"; + Alias = "noogle"; + Description = "Search with noogle"; + } + + { + Name = "NixOS Options"; + URLTemplate = "https://search.nixos.org/options?query={searchTerms}"; + Method = "GET"; + IconURL = "https://search.nixos.org/favicon.png"; + Alias = "nixos"; + Description = "Search in NixOS options"; + } + + { + Name = "Nix Packages"; + URLTemplate = "https://search.nixos.org/packages?query={searchTerms}"; + Method = "GET"; + IconURL = "https://search.nixos.org/favicon.png"; + Alias = "nixpkgs"; + Description = "Search in nixpkgs"; + } + ]; + }; + + DisableSetDesktopBackground = true; + + # disable tracking bullshit + DisableTelemetry = true; + DisablePocket = true; + DisableFirefoxStudies = true; + DisableFeedbackCommands = true; + + # disable password manager + PasswordManagerEnabled = false; + OfferToSaveLogins = false; + PrimaryPassword = false; + AutofillCreditCardEnabled = false; + + # disable bookmark-toolbar + NoDefaultBookmarks = true; + DisplayBookmarksToolbar = "never"; + DisplayMenuBar = "default-off"; + + Homepage.StartPage = "previous-session"; + Homepage.Locked = true; + + # disable DoH + DNSOverHTTPS.Enabled = false; + DNSOverHTTPS.Locked = true; + + # enable tracking protection + EnableTrackingProtection = { + Cryptomining = true; + Fingerprinting = true; + Value = true; + Locked = true; + }; + + UserMessaging = { + ExtensionRecommendations = false; + FeatureRecommendations = false; + UrlbarInterventions = false; + SkipOnboarding = true; + MoreFromMozilla = false; + FirefoxLabs = false; + Locked = true; + }; + + Handlers.schemes = + let + handler = { + action = "useHelperApp"; + ask = true; + handlers = [ + { + name = "VDV PKPass DB Navigator Hook"; + path = pkgs.writeShellScript "db-hook" '' + #!/usr/bin/env bash + + if [[ "$1" == "dbnav:"* ]]; then + url=$(echo -n $1 | base64) + xdg-open "https://vdv-pkpass.magicalcodewit.ch/account/db_login/callback?url=$url" + elif [[ "$1" == "bahnbonus:"* ]]; then + url=$(echo -n $1 | base64) + xdg-open "https://vdv-pkpass.magicalcodewit.ch/account/bahnbonus_login/callback?url=$url" + else + xdg-open "$1" + fi + ''; + } + ]; + }; + in + { + dbnav = handler; + bahnbonus = handler; + }; + + Preferences = + lib.mapAttrs + (_name: value: { + Status = "locked"; + Value = value; + }) + { + "browser.uitour.enabled" = false; + # "pinch-to-zoom" zoom with alt+mousewheel + "mousewheel.with_alt.action" = 5; + + # disable some password-manager/autofill features + "signon.generation.enabled" = false; + "signon.autofillForms" = false; + "signon.firefoxRelay.feature" = "disabled"; + "signon.management.page.breach-alerts.enabled" = false; + + # ask websites to not share or sell data + "privacy.globalprivacycontrol.enabled" = true; + + # disable AI bullshit + "browser.ml.smartAssist.enabled" = false; + "browser.ml.pageAssist.enabled" = false; + "browser.ml.chat.sidebar" = false; + "browser.ml.chat.shortcuts" = false; + "browser.ml.enable" = false; + "browser.ml.chat.enabled" = false; + "browser.ml.linkPreview.enabled" = false; + "browser.tabs.groups.smart.enabled" = false; + "browser.tabs.groups.smart.userEnabled" = false; + "extensions.ml.enabled" = false; + + # disable warning on `abou:config` + "browser.aboutConfig.showWarning" = false; + + # disable telemetry stuff + "browser.ping-centre.telemetry" = false; + "browser.topsites.contile.enabled" = false; + "browser.crashReports.unsubmittedCheck.autoSubmit2" = false; + + # just open previous session + "browser.startup.page" = 3; + + # disable trending search suggestions + "browser.urlbar.suggest.trending" = false; + + # disable 'fancy' newtab page bullshit with tons of tracking and ads + "browser.startup.homepage" = "chrome://browser/content/blanktab.html"; + "browser.newtabpage.enabled" = false; + "browser.newtabpage.activity-stream.telemetry" = false; + "browser.newtabpage.activity-stream.showSearch" = false; + "browser.newtabpage.activity-stream.showSponsored" = false; + "browser.newtabpage.activity-stream.showSponsoredTopSites" = false; + "browser.newtabpage.activity-stream.feeds.telemetry" = false; + "browser.newtabpage.activity-stream.feeds.topsites" = false; + "browser.newtabpage.activity-stream.feeds.snippets" = false; + "browser.newtabpage.activity-stream.feeds.system.topsites" = false; + "browser.newtabpage.activity-stream.feeds.section.highlights" = false; + "browser.newtabpage.activity-stream.section.highlights.includePocket" = false; + "browser.newtabpage.activity-stream.section.highlights.includeBookmarks" = false; + "browser.newtabpage.activity-stream.section.highlights.includeDownloads" = false; + "browser.newtabpage.activity-stream.section.highlights.includeVisited" = false; + }; + + ExtensionSettings = + lib.pipe + [ + # Bitwarden Password Manager + [ + "navbar" + "{446900e4-71c2-419f-a6a7-df9c091e268b}" + ] + # uBlock Origin + [ + "navbar" + "uBlock0@raymondhill.net" + ] + # uBlacklist + [ + "menupanel" + "@ublacklist" + ] + # Facebook Container + [ + "menupanel" + "@contain-facebook" + ] + # Google Container + [ + "menupanel" + "@contain-google" + ] + # Containerise + [ + "menupanel" + "containerise@kinte.sh" + ] + # cookies.txt + [ + "menupanel" + "{12cf650b-1822-40aa-bff0-996df6948878}" + ] + # Firefox Multi-Account Containers + [ + "menupanel" + "@testpilot-containers" + ] + # IndicateTLS + [ + "menupanel" + "{252ee273-8c8d-4609-b54d-62ae345be0a1}" + ] + # IPvFoo + [ + "menupanel" + "ipvfoo@pmarks.net" + ] + # Modern for Wikipedia + [ + "menupanel" + "{e9090647-32ff-48e4-9c3c-1361e8fd270e}" + ] + # SponsorBlock for YouTube - Skip Sponsorships + [ + "menupanel" + "sponsorBlocker@ajay.app" + ] + # German Dictionary + [ + "menupanel" + "de-DE@dictionaries.addons.mozilla.org" + ] + ] + [ + (lib.map (config: { + name = lib.elemAt config 1; + value = { + installation_mode = "normal_installed"; + default_area = lib.elemAt config 0; + install_url = "https://addons.mozilla.org/firefox/downloads/latest/${lib.elemAt config 1}/latest.xpi"; + }; + })) + lib.listToAttrs + ]; + + }; + }) + ); + }; + }; + +}
diff --git a/nixosModules/zpha/programs/fish.nix b/nixosModules/zpha/programs/fish.nix @@ -0,0 +1,21 @@ +{ + config, + lib, + ... +}: + +{ + + options.zpha.programs.fish.enable = lib.mkEnableOption "fish"; + + config = lib.mkIf config.zpha.programs.fish.enable { + preservation.preserveAt."/persist".users.zaphyra.files = [ + ".local/share/fish/fish_history" + ]; + + users.users.zaphyra.maid = { + programs.fish.enable = true; + }; + }; + +}
diff --git a/nixosModules/zpha/programs/gajim.nix b/nixosModules/zpha/programs/gajim.nix @@ -0,0 +1,23 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.programs.gajim.enable = lib.mkEnableOption "gajim"; + + config = lib.mkIf config.zpha.programs.gajim.enable { + preservation.preserveAt."/persist".users.zaphyra.directories = [ + ".local/share/gajim" + ".config/gajim" + ]; + + users.users.zaphyra.maid = { + packages = [ pkgs.gajim ]; + }; + }; + +}
diff --git a/nixosModules/zpha/programs/ghostty.nix b/nixosModules/zpha/programs/ghostty.nix @@ -0,0 +1,45 @@ +{ + lib, + pkgs, + config, + ... +}: +let + cfg = config.zpha.programs.ghostty; + settingsFormat = pkgs.formats.keyValue { + listsAsDuplicateKeys = true; + mkKeyValue = lib.generators.mkKeyValueDefault { } " = "; + }; + +in +{ + + options.zpha.programs.ghostty = { + enable = lib.mkEnableOption "ghostty"; + settings = lib.mkOption { + default = { }; + type = settingsFormat.type; + }; + }; + + config = lib.mkIf cfg.enable { + zpha.programs.ghostty.settings = { + background-opacity = 0.8; + background-blur = true; + + clipboard-trim-trailing-spaces = true; + clipboard-paste-protection = false; + }; + + users.users.zaphyra.maid = { + environment.sessionVariables.TERMINAL = "ghostty"; + + packages = [ pkgs.ghostty ]; + + file.xdg_config = { + "ghostty/config".source = settingsFormat.generate "ghostty.ini" cfg.settings; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/programs/git.nix b/nixosModules/zpha/programs/git.nix @@ -0,0 +1,82 @@ +{ + lib, + config, + ... +}: + +{ + + options.zpha.programs.git.enable = lib.mkEnableOption "zaphyras git config"; + + config = lib.mkIf config.zpha.programs.git.enable { + users.users.zaphyra.maid = { + programs = { + git = { + enable = true; + + ignores = [ + ".DS_Store" + "*.swp" + ]; + + settings = { + user = { + name = "Katja Ramona Sophie Kwast (zaphyra)"; + email = "git@zaphyra.eu"; + }; + + aliases = { + plog = "log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(bold yellow)%d%C(reset)' --all"; + log-gpg = "log --show-signature"; + fixup = "commit --fixup"; + fuck = "reset HEAD~1"; + pfusch = "push --force-with-lease --force-if-includes"; + yikes = "commit --amend --no-edit"; + }; + + init.defaultBranch = "main"; + + diff.algorithm = "histogram"; + + commit.verbose = true; + + rerere.enabled = true; + + merge.conflictstyle = "zdiff3"; + + rebase = { + autosquash = true; + autostash = true; + }; + + push = { + default = "current"; + autoSetupRemote = true; + }; + + pull.rebase = true; + + transfer.fsckobjects = true; + fetch.fsckobjects = true; + receive.fsckObjects = true; + }; + }; + + lazygit = { + enable = true; + settings = { + disableStartupPopups = true; + gui.nerdFontsVersion = "3"; + git.overrideGpg = true; + os.editPreset = "micro"; + }; + }; + + fish.aliases = { + reinitgit = "rm -rf .git && git init && git add -A && git commit -m 'init'"; + }; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/programs/gnome-calendar.nix b/nixosModules/zpha/programs/gnome-calendar.nix @@ -0,0 +1,19 @@ +{ + config, + systemConfig, + lib, + pkgs, + ... +}: + +{ + + options.zpha.programs.gnome-calendar.enable = lib.mkEnableOption "gnome-calendar"; + + config = lib.mkIf config.zpha.programs.gnome-calendar.enable { + zpha.programs.gnome-online-accounts.enable = true; + + users.users.zaphyra.maid.packages = with pkgs; [ gnome-calendar ]; + }; + +}
diff --git a/nixosModules/zpha/programs/gnome-online-accounts.nix b/nixosModules/zpha/programs/gnome-online-accounts.nix @@ -0,0 +1,53 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + options.zpha.programs.gnome-online-accounts.enable = + lib.mkEnableOption "Enable gnome-online-accounts and evolution-data-server"; + + config = lib.mkIf config.zpha.programs.gnome-online-accounts.enable ( + let + gnome-online-accounts = pkgs.gnome-online-accounts.overrideAttrs (prevAttrs: { + mesonFlags = prevAttrs.mesonFlags ++ [ + "-Dexchange=false" + "-Dgoogle=false" + "-Dkerberos=false" + "-Downcloud=false" + "-Dms_graph=false" + ]; + }); + + in + { + preservation.preserveAt."/persist".users.zaphyra.directories = [ + ".local/share/evolution" + ".config/goa-1.0" + ]; + + users.users.zaphyra.maid = { + dbus.packages = [ gnome-online-accounts ]; + packages = with pkgs; [ + gnome-online-accounts-gtk + (evolution-data-server-gtk4.override { + withGtk4 = true; + enableOAuth2 = false; + }) + ]; + systemd.services.gnome-online-accounts = { + unitConfig.Description = "Gnome Online Accounts"; + serviceConfig = { + Type = "dbus"; + ExecStart = "${gnome-online-accounts}/libexec/goa-daemon"; + BusName = "org.gnome.OnlineAccounts"; + }; + }; + }; + + } + ); + +}
diff --git a/nixosModules/zpha/programs/gnome-text-editor.nix b/nixosModules/zpha/programs/gnome-text-editor.nix @@ -0,0 +1,30 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.programs.gnome-text-editor.enable = lib.mkEnableOption "gnome-text-editor"; + + config = lib.mkIf config.zpha.programs.gnome-text-editor.enable { + users.users.zaphyra.maid = { + packages = [ pkgs.gnome-text-editor ]; + + # dconf.settings = { + # "org/gnome/TextEditor" = { + # highlight-current-line = true; + # wrap-text = true; + # show-line-numbers = true; + # show-map = true; + # show-right-margin = true; + # indent-style = "space"; + # indent-width = 4; + # }; + # }; + }; + }; + +}
diff --git a/nixosModules/zpha/programs/gpg.nix b/nixosModules/zpha/programs/gpg.nix @@ -0,0 +1,194 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.programs.gpg.enable = lib.mkEnableOption "gpg"; + + config = lib.mkIf config.zpha.programs.gpg.enable { + preservation.preserveAt."/persist".users.zaphyra.directories = lib.singleton { + mode = "0700"; + directory = ".config/gnupg"; + }; + + users.users.zaphyra.maid = { + packages = with pkgs; [ + pcsc-tools + gnupg + ]; + + environment.sessionVariables = { + GNUPGHOME = "/home/zaphyra/.config/gnupg"; + }; + + programs.fish.config = '' + set -e SSH_AGENT_PID + + begin + set -l gnupg_val 0 + if set -q gnupg_SSH_AUTH_SOCK_by + set gnupg_val $gnupg_SSH_AUTH_SOCK_by + end + + if test $gnupg_val -ne %self + set -x SSH_AUTH_SOCK (${lib.getExe' pkgs.gnupg "gpgconf"} --list-dirs agent-ssh-socket) + end + end + ''; + + programs.git.settings = { + user.signingKey = "BFE6386C8D66BCD4DAE14FC895F0FE7CD7E6A022"; + commit.gpgSign = true; + }; + + file.xdg_config = { + "gnupg/gpg.conf".text = '' + keyserver "hkps://keyserver.ubuntu.com:443" + ''; + "gnupg/scdaemon.conf".text = '' + disable-ccid + ''; + "gnupg/gpg-agent.conf".text = '' + enable-ssh-support + + default-cache-ttl 600 + default-cache-ttl-ssh 600 + + pinentry-program "${lib.getExe pkgs.pinentry-gnome3}" + ''; + "gnupg/sshcontrol".text = '' + 595A90924C65444EF00F0890BA85DE2A57DD79B2 + ''; + }; + + systemd = { + services.gpg-agent = { + unitConfig = { + Description = "GnuPG cryptographic agent and passphrase cache"; + Documentation = "man:gpg-agent(1)"; + Requires = "gpg-agent.socket"; + After = "gpg-agent.socket"; + # This is a socket-activated service: + RefuseManualStart = true; + }; + + serviceConfig = { + ExecStart = "${lib.getExe' pkgs.gnupg "gpg-agent"} --supervised"; + ExecReload = "${lib.getExe' pkgs.gnupg "gpgconf"} --reload gpg-agent"; + Environment = [ "GNUPGHOME=~/.config/gnupg" ]; + }; + }; + + sockets = + let + hexStringToBase32 = + let + mod = a: b: a - a / b * b; + pow2 = lib.elemAt [ + 1 + 2 + 4 + 8 + 16 + 32 + 64 + 128 + 256 + ]; + + base32Alphabet = lib.stringToCharacters "ybndrfg8ejkmcpqxot1uwisza345h769"; + hexToIntTable = lib.listToAttrs ( + lib.genList (x: { + name = lib.toLower (lib.toHexString x); + value = x; + }) 16 + ); + + initState = { + ret = ""; + buf = 0; + bufBits = 0; + }; + go = + { + ret, + buf, + bufBits, + }: + hex: + let + buf' = buf * pow2 4 + hexToIntTable.${hex}; + bufBits' = bufBits + 4; + extraBits = bufBits' - 5; + in + if bufBits >= 5 then + { + ret = ret + lib.elemAt base32Alphabet (buf' / pow2 extraBits); + buf = mod buf' (pow2 extraBits); + bufBits = bufBits' - 5; + } + else + { + inherit ret; + buf = buf'; + bufBits = bufBits'; + }; + in + hexString: (lib.foldl' go initState (lib.stringToCharacters hexString)).ret; + + gpgconf = + dir: + let + hash = lib.substring 0 24 ( + hexStringToBase32 (builtins.hashString "sha1" "/home/zaphyra/.config/gnupg") + ); + subdir = "d.${hash}/${dir}"; + in + "%t/gnupg/${subdir}"; + mkSocket = + { + desc, + docs, + stream, + fdName, + }: + { + wantedBy = [ "sockets.target" ]; + unitConfig = { + Description = desc; + Documentation = docs; + }; + socketConfig = { + ListenStream = gpgconf "${stream}"; + FileDescriptorName = fdName; + Service = "gpg-agent.service"; + SocketMode = "0600"; + DirectoryMode = "0700"; + }; + }; + in + { + gpg-agent = mkSocket { + desc = "GnuPG cryptographic agent and passphrase cache"; + docs = "man:gpg-agent(1)"; + stream = "S.gpg-agent"; + fdName = "std"; + }; + + gpg-agent-ssh = mkSocket { + desc = "GnuPG cryptographic agent (ssh-agent emulation)"; + docs = "man:gpg-agent(1) man:ssh-add(1) man:ssh-agent(1) man:ssh(1)"; + stream = "S.gpg-agent.ssh"; + fdName = "ssh"; + }; + }; + }; + }; + + }; + +}
diff --git a/nixosModules/zpha/programs/htop.nix b/nixosModules/zpha/programs/htop.nix @@ -0,0 +1,42 @@ +{ + lib, + pkgs, + config, + ... +}: +let + cfg = config.zpha.programs.htop; + settingsFormat = pkgs.formats.keyValue { + listsAsDuplicateKeys = true; + mkKeyValue = lib.generators.mkKeyValueDefault { } "="; + }; + +in +{ + + options.zpha.programs.htop = { + enable = lib.mkEnableOption "htop"; + settings = lib.mkOption { + default = { }; + type = settingsFormat.type; + }; + }; + + config = lib.mkIf cfg.enable { + zpha.programs.htop.settings = { + hide_userland_threads = 1; + tree_view = 1; + show_program_path = 0; + show_cpu_frequency = 1; + }; + + users.users.zaphyra.maid = { + packages = [ (pkgs.htop.override { sensorsSupport = true; }) ]; + + file.xdg_config = { + "htop/htoprc".source = settingsFormat.generate "htoprc" cfg.settings; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/programs/loupe.nix b/nixosModules/zpha/programs/loupe.nix @@ -0,0 +1,44 @@ +{ + config, + lib, + pkgs, + ... +}: +{ + + options.zpha.programs.loupe.enable = lib.mkEnableOption "loupe image viewer"; + + config = lib.mkIf config.zpha.programs.loupe.enable { + users.users.zaphyra.maid = { + packages = [ pkgs.loupe ]; + + xdg.mime-apps = { + defaultApplications = lib.genAttrs [ + "image/jpeg" + "image/png" + "image/gif" + "image/webp" + "image/tiff" + "mage/bmp" + "image/svg+xml" + "image/svg+xml-compressed" + "image/avif" + "image/heic" + ] (_name: [ "org.gnome.Loupe.desktop" ]); + addedAssociations = lib.genAttrs [ + "image/jpeg" + "image/png" + "image/gif" + "image/webp" + "image/tiff" + "mage/bmp" + "image/svg+xml" + "image/svg+xml-compressed" + "image/avif" + "image/heic" + ] (_name: [ "org.gnome.Loupe.desktop" ]); + }; + }; + }; + +}
diff --git a/nixosModules/zpha/programs/micro.nix b/nixosModules/zpha/programs/micro.nix @@ -0,0 +1,216 @@ +{ + lib, + pkgs, + config, + ... +}: +let + inherit (lib) types; + cfg = config.zpha.programs.micro; + +in +{ + + options.zpha.programs.micro = { + enable = lib.mkEnableOption "micro editor"; + lsp-servers = lib.mkOption { + default = { }; + type = types.attrsOf ( + types.submodule { + options = { + command = lib.mkOption { type = types.str; }; + options = lib.mkOption { + inherit ((pkgs.formats.json { })) type; + default = { }; + }; + }; + } + ); + }; + }; + + config = lib.mkIf cfg.enable { + + users.users.zaphyra.maid = { + environment.sessionVariables.EDITOR = "micro"; + + packages = [ pkgs.micro ]; + + file.xdg_config = { + # main config + "micro/settings.json".text = builtins.toJSON { + "*.nix".tabstospaces = true; + "*.nix".tabmovement = true; + "*.nix".tabsize = 2; + + "*.nim".tabstospaces = true; + "*.nim".tabmovement = true; + "*.nim".tabsize = 2; + + clipboard = "terminal"; + + scrollbar = true; + eofnewline = false; + wordwrap = true; + softwrap = true; + + ignorecase = true; + savehistory = false; + + "lsp.formatOnSave" = true; + "lsp.tabcompletion" = true; + "lsp.autocompleteDetails" = false; + "lsp.server" = lib.concatMapAttrsStringSep "," ( + name: value: + lib.concatStringsSep "=" ( + [ + name + value.command + ] + ++ (lib.optionals (lib.hasAttr "options" value) [ + (builtins.toJSON value.options) + ]) + ) + ) cfg.lsp-servers; + }; + + # keybindings + "micro/bindings.json".text = builtins.toJSON { + "Alt-," = "PreviousTab"; + "Alt-." = "NextTab"; + "Alt-Backspace" = "DeleteWordLeft"; + "Alt-CtrlH" = "DeleteWordLeft"; + "Alt-F" = "FindLiteral"; + "Alt-[" = "DiffPrevious|CursorStart"; + "Alt-]" = "DiffNext|CursorEnd"; + "Alt-a" = "StartOfLine"; + "Alt-b" = "WordLeft"; + "Alt-c" = "RemoveAllMultiCursors"; + "Alt-e" = "EndOfLine"; + "Alt-f" = "WordRight"; + "Alt-g" = "ToggleKeyMenu"; + "Alt-m" = "SpawnMultiCursorSelect"; + "Alt-n" = "SpawnMultiCursor"; + "Alt-p" = "RemoveMultiCursor"; + "Alt-x" = "SkipMultiCursor"; + "Alt-{" = "ParagraphPrevious"; + "Alt-}" = "ParagraphNext"; + "AltDown" = "MoveLinesDown"; + "AltLeft" = "StartOfTextToggle"; + "AltRight" = "EndOfLine"; + "AltShiftDown" = "SpawnMultiCursorDown"; + "AltShiftLeft" = "SelectToStartOfTextToggle"; + "AltShiftRight" = "SelectToEndOfLine"; + "AltShiftUp" = "SpawnMultiCursorUp"; + "AltUp" = "MoveLinesUp"; + "Backspace" = "Backspace"; + "Backtab" = "OutdentSelection|OutdentLine"; + "Ctrl-MouseLeft" = "MouseMultiCursor"; + "Ctrl-a" = "SelectAll"; + "Ctrl-b" = "ShellMode"; + "Ctrl-c" = "Copy|CopyLine"; + "Ctrl-d" = "DuplicateLine"; + "Ctrl-e" = "CommandMode"; + "Ctrl-f" = "Find"; + "Ctrl-g" = "ToggleHelp"; + "Ctrl-h" = "Backspace"; + "Ctrl-j" = "PlayMacro"; + "Ctrl-k" = "CutLine"; + "Ctrl-l" = "command-edit:goto "; + "Ctrl-n" = "FindNext"; + "Ctrl-o" = "OpenFile"; + "Ctrl-p" = "FindPrevious"; + "Ctrl-q" = "Quit"; + "Ctrl-r" = "ToggleRuler"; + "Ctrl-s" = "Save"; + "Ctrl-t" = "AddTab"; + "Ctrl-u" = "ToggleMacro"; + "Ctrl-v" = "Paste"; + "Ctrl-w" = "NextSplit"; + "Ctrl-x" = "Cut|CutLine"; + "Ctrl-y" = "Redo"; + "Ctrl-z" = "Undo"; + "CtrlDown" = "CursorEnd"; + "CtrlEnd" = "CursorEnd"; + "CtrlHome" = "CursorStart"; + "CtrlPageDown" = "NextTab"; + "CtrlPageUp" = "PreviousTab"; + "AltPageDown" = "NextTab"; + "AltPageUp" = "PreviousTab"; + "CtrlShiftDown" = "SelectToEnd"; + "CtrlShiftLeft" = "SelectWordLeft"; + "CtrlShiftRight" = "SelectWordRight"; + "CtrlShiftUp" = "SelectToStart"; + "CtrlUp" = "CursorStart"; + "Delete" = "Delete"; + "Down" = "CursorDown"; + "End" = "EndOfLine"; + "Enter" = "InsertNewline"; + "Esc" = "Escape"; + "F10" = "Quit"; + "F2" = "Save"; + "F3" = "Find"; + "F4" = "Quit"; + "F7" = "Find"; + "Home" = "StartOfText"; + "Insert" = "ToggleOverwriteMode"; + "Left" = "CursorLeft"; + "MouseLeft" = "MousePress"; + "MouseLeftDrag" = "MouseDrag"; + "MouseLeftRelease" = "MouseRelease"; + "MouseMiddle" = "PastePrimary"; + "MouseWheelDown" = "ScrollDown"; + "MouseWheelUp" = "ScrollUp"; + "PageDown" = "CursorPageDown"; + "PageUp" = "CursorPageUp"; + "Right" = "CursorRight"; + "ShiftDown" = "SelectDown"; + "ShiftEnd" = "SelectToEndOfLine"; + "ShiftHome" = "SelectToStartOfTextToggle"; + "ShiftLeft" = "SelectLeft"; + "ShiftPageDown" = "SelectPageDown"; + "ShiftPageUp" = "SelectPageUp"; + "ShiftRight" = "SelectRight"; + "ShiftUp" = "SelectUp"; + "Tab" = "Autocomplete|IndentSelection|InsertTab"; + "Up" = "CursorUp"; + + # plugins + "Alt-/" = "lua:comment.comment"; + "Alt-d" = "command:definition"; + # "Alt-f" = "command:format"; + "Alt-k" = "command:hover"; + "Alt-r" = "command:references"; + "CtrlSpace" = "command:lspcompletion"; + "CtrlUnderscore" = "lua:comment.comment"; + "F1" = "command:cheat"; + # "F4" = "command:jumptag"; + "F5" = "lua:wc.wordCount"; + }; + + # plugins + "micro/plug/lsp".source = pkgs.fetchFromGitHub { + owner = "AndCake"; + repo = "micro-plugin-lsp"; + rev = "v0.6.3"; + sha256 = "sha256-rZ9Vw9WPGNaJBGHKU40F6cBIYQ1JFtSKPDrheazKkPY="; + }; + + "micro/plug/detectindent".source = pkgs.fetchFromGitHub { + owner = "dmaluka"; + repo = "micro-detectindent"; + rev = "v1.1.0"; + sha256 = "sha256-5bKEkOnhz0pyBR2UNw5vvYiTtpd96fBPTYW9jnETvq4="; + }; + + "micro/plug/wc".source = pkgs.fetchFromGitHub { + owner = "adamnpeace"; + repo = "micro-wc-plugin"; + rev = "b2c9957e521770eadc1ecae9d54c0a30f40a0a3d"; + sha256 = "sha256-Z6MC2cet8+7XHv41G+SlAZViCqlh/9dk0CSt7HklnTg="; + }; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/programs/nautilus.nix b/nixosModules/zpha/programs/nautilus.nix @@ -0,0 +1,69 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.programs.nautilus.enable = lib.mkEnableOption "nautilus"; + + config = lib.mkIf config.zpha.programs.nautilus.enable { + + users.users.zaphyra.maid = { + packages = with pkgs; [ + nautilus + sushi # quick-preview for nautilus + ]; + + environment.sessionVariables = { + NAUTILUS_4_EXTENSION_DIR = "/etc/profiles/per-user/zaphyra/lib/nautilus/extensions-4"; + }; + + gsettings.settings = { + org.gnome.nautilus = { + preferences = { + date-time-format = "detailed"; + default-folder-viewer = "list-view"; + show-delete-permanently = true; + }; + list-view = { + use-tree-view = true; + default-zoom-level = "small"; + }; + }; + }; + + xdg.mime-apps.defaultApplications = lib.genAttrs [ + "inode/directory" + "application/x-7z-compressed" + "application/x-7z-compressed-tar" + "application/x-bzip" + "application/x-bzip-compressed-tar" + "application/x-compress" + "application/x-compressed-tar" + "application/x-cpio" + "application/x-gzip" + "application/x-lha" + "application/x-lzip" + "application/x-lzip-compressed-tar" + "application/x-lzma" + "application/x-lzma-compressed-tar" + "application/x-tar" + "application/x-tarz" + "application/x-xar" + "application/x-xz" + "application/x-xz-compressed-tar" + "application/zip" + "application/gzip" + "application/bzip2" + "application/x-bzip2-compressed-tar" + "application/vnd.rar;application/zstd" + "application/x-zstd-compressed-tar" + ] (_name: [ "org.gnome.Nautilus.desktop" ]); + }; + + }; + +}
diff --git a/nixosModules/zpha/programs/niri/enable.nix b/nixosModules/zpha/programs/niri/enable.nix @@ -0,0 +1,65 @@ +{ + lib, + pkgs, + config, + npins, + ... +}: +let + inherit (lib) types; + cfg = config.zpha.programs.niri; + + niri-nix = import "${npins.niri-nix}/lib" { + self = npins.niri-nix; + inherit lib; + nixpkgs.legacyPackages."${pkgs.stdenv.hostPlatform.system}" = pkgs; + }; +in +{ + + options.zpha.programs.niri = { + enable = lib.mkEnableOption "niri"; + settings = lib.mkOption { + type = + with lib.types; + let + valueType = + nullOr (oneOf [ + bool + int + float + str + path + (attrsOf valueType) + (listOf valueType) + ]) + // { + description = "Niri configuration value"; + }; + in + types.submodule { + freeformType = valueType; + }; + default = { }; + }; + + extraConfig = lib.mkOption { + type = types.lines; + default = ""; + }; + + finalConfig = lib.mkOption { + type = types.lines; + default = (niri-nix.mkNiriKDL cfg.settings) + "\n" + cfg.extraConfig; + }; + }; + + config = lib.mkIf cfg.enable { + common.programs.niri.enable = true; + users.users.zaphyra.maid = { + file.xdg_config."niri/config.kdl".source = + niri-nix.validatedConfigFor config.programs.niri.package cfg.finalConfig; + }; + }; + +}
diff --git a/nixosModules/zpha/programs/niri/gtk.nix b/nixosModules/zpha/programs/niri/gtk.nix @@ -0,0 +1,58 @@ +{ + lib, + config, + pkgs, + ... +}: +{ + + config = lib.mkIf config.zpha.programs.niri.enable { + users.users.zaphyra.maid = { + packages = with pkgs; [ + adwaita-icon-theme + zpha.adwaita-colors-icon-theme + ]; + + environment.sessionVariables = { + GTK_IM_MODULE = "simple"; + }; + + gsettings.settings = { + org.gnome.desktop = { + interface = { + color-scheme = "prefer-dark"; + font-name = "Adwaita Sans 11"; + accent-color = "green"; + gtk-theme = "Adwaita"; + icon-theme = "Adwaita-green"; + cursor-theme = "Adwaita"; + }; + wm.preferences = { + button-layout = ""; + }; + }; + org.gtk.Settings.Debug = { + enable-inspector-keybinding = true; + }; + }; + + file.xdg_config."gtk-3.0/settings.ini".text = lib.generators.toINI { } { + Settings = + let + gnomeSettings = config.users.users.zaphyra.maid.gsettings.settings.org.gnome.desktop.interface; + in + { + gtk-application-prefer-dark-theme = gnomeSettings.color-scheme == "prefer-dark"; + gtk-theme-name = gnomeSettings.gtk-theme; + gtk-icon-theme-name = gnomeSettings.icon-theme; + gtk-cursor-theme-name = gnomeSettings.cursor-theme; + gtk-font-name = gnomeSettings.font-name; + }; + }; + }; + + zpha.programs.niri.settings.cursor.xcursor-theme = + config.users.users.zaphyra.maid.gsettings.settings.org.gnome.desktop.interface.cursor-theme; + }; + +}
diff --git a/nixosModules/zpha/programs/niri/packages.nix b/nixosModules/zpha/programs/niri/packages.nix @@ -0,0 +1,17 @@ +{ + lib, + config, + pkgs, + ... +}: +{ + + config = lib.mkIf config.zpha.programs.niri.enable { + users.users.zaphyra.maid.packages = with pkgs; [ + xdg-utils + wl-clipboard-rs + adw-gtk3 + ]; + }; + +}
diff --git a/nixosModules/zpha/programs/niri/rules.nix b/nixosModules/zpha/programs/niri/rules.nix @@ -0,0 +1,121 @@ +{ config, lib, ... }: + +{ + + config = lib.mkIf config.zpha.programs.niri.enable { + zpha.programs.niri.settings = { + layer-rule = [ + { + match = [ + { + _props = { + namespace = "shaderbg"; + }; + } + ]; + place-within-backdrop = true; + } + ]; + + window-rule = [ + { + clip-to-geometry = true; + geometry-corner-radius = 6.0; + } + { + match = [ + { + _props = { + is-floating = true; + }; + } + ]; + shadow.on = [ ]; + } + { + match = [ + { + _props = { + at-startup = true; + app-id = "^org.gnome.Fractal$"; + }; + } + { + _props = { + at-startup = true; + app-id = "^im.dino.Dino$"; + }; + } + { + _props = { + at-startup = true; + app-id = "^org.gajim.Gajim$"; + }; + } + ]; + + default-column-display = "tabbed"; + open-on-workspace = "chat"; + } + { + match = [ + { + _props = { + app-id = "firefox$"; + }; + } + { + _props = { + app-id = "thunderbird$"; + }; + } + ]; + + open-maximized = true; + } + { + match = [ + { + _props = { + app-id = "^org.gnome.NautilusPreviewer$"; + }; + } + ]; + + open-floating = true; + } + { + match = [ + { + _props = { + app-id = "^thunderbird$"; + }; + } + ]; + + open-on-workspace = "mail"; + } + { + match = [ + { + _props = { + app-id = "firefox$"; + title = "^Picture-in-Picture$"; + }; + } + ]; + + open-floating = true; + default-column-width.fixed = 480; + default-window-height.fixed = 270; + default-floating-position._props = { + x = 32; + y = 32; + relative-to = "bottom-right"; + }; + } + ]; + }; + }; + +}
diff --git a/nixosModules/zpha/programs/niri/settings.nix b/nixosModules/zpha/programs/niri/settings.nix @@ -0,0 +1,220 @@ +{ + lib, + config, + pkgs, + ... +}: +{ + + config = lib.mkIf config.zpha.programs.niri.enable { + zpha.programs.niri.settings = { + environment = { + QT_QPA_PLATFORM = "wayland"; + QT_WAYLAND_FORCE_DPI = "physical"; + QT_WAYLAND_DISABLE_WINDOWDECORATION = "1"; + }; + + workspace = [ + { _args = [ "browser" ]; } + { _args = [ "mail" ]; } + { _args = [ "terminal" ]; } + { _args = [ "chat" ]; } + ]; + + prefer-no-csd = [ ]; + + gestures.hot-corners = [ ]; + + layout = { + always-center-single-column = true; + gaps = 15; + background-color = "transparent"; + focus-ring = { + width = 2; + inactive-color = "rgba(59, 135, 86, .9)"; + active-color = "rgba(74, 169, 108, .9)"; + }; + + tab-indicator = { + gaps-between-tabs = 10; + position = "top"; + }; + }; + + input = { + workspace-auto-back-and-forth = [ ]; + + focus-follows-mouse._props = { + max-scroll-amount = "0%"; + }; + + touchpad = { + natural-scroll = [ ]; + click-method = "clickfinger"; + }; + + keyboard.xkb = { + layout = "us,de,ru"; + variant = "mac,qwerty,mac"; + options = "grp:alt_shift_toggle"; + }; + }; + + binds = + (lib.mergeAttrsList ( + lib.map + (num: { + "Mod+${toString num}" = lib.mkDefault { focus-workspace = num; }; + "Mod+Shift+${toString num}" = lib.mkDefault { + move-column-to-workspace = num; + }; + }) + [ + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + ] + )) + // { + "Mod+1".focus-workspace = "browser"; + "Mod+Shift+1".move-column-to-workspace = "browser"; + "Mod+2".focus-workspace = "mail"; + "Mod+Shift+2".move-column-to-workspace = "mail"; + "Mod+3".focus-workspace = "terminal"; + "Mod+Shift+3".move-column-to-workspace = "terminal"; + "Mod+4".focus-workspace = "chat"; + "Mod+Shift+4".move-column-to-workspace = "chat"; + + "Mod+Shift+E".quit = [ ]; + "Mod+Z".power-off-monitors = [ ]; + + "Mod+Space".spawn = lib.mkDefault [ (lib.getExe pkgs.fuzzel) ]; + "Mod+Return".spawn = [ + (lib.getExe pkgs.unstable.app2unit) + "-T" + ]; + "Mod+L".spawn = [ + (lib.getExe' pkgs.systemd "loginctl") + "lock-session" + ]; + + "Mod+W" = { + _props.repeat = false; + close-window = [ ]; + }; + + "XF86AudioMute" = { + _props.allow-when-locked = true; + spawn = lib.mkDefault [ + "wpctl" + "set-mute" + "@DEFAULT_AUDIO_SINK@" + "toggle" + ]; + }; + "XF86AudioRaiseVolume" = { + _props.allow-when-locked = true; + spawn = lib.mkDefault [ + "wpctl" + "set-volume" + "@DEFAULT_AUDIO_SINK@" + ".5%+" + ]; + }; + "XF86AudioLowerVolume" = { + _props.allow-when-locked = true; + spawn = lib.mkDefault [ + "wpctl" + "set-volume" + "@DEFAULT_AUDIO_SINK@" + ".5%-" + ]; + }; + + "XF86MonBrightnessUp" = { + _props.allow-when-locked = true; + spawn = lib.mkDefault [ + (lib.getExe pkgs.brightnessctl) + "s" + "+2%" + ]; + }; + "XF86MonBrightnessDown" = { + _props.allow-when-locked = true; + spawn = lib.mkDefault [ + (lib.getExe pkgs.brightnessctl) + "s" + "2%-" + ]; + }; + + "Mod+Tab".toggle-overview = [ ]; + + "Mod+Prior".focus-workspace-up = [ ]; + "Mod+Next".focus-workspace-down = [ ]; + "Mod+WheelScrollDown" = { + _props.cooldown-ms = 150; + focus-workspace-down = [ ]; + }; + "Mod+WheelScrollUp" = { + _props.cooldown-ms = 150; + focus-workspace-up = [ ]; + }; + "Mod+WheelScrollRight".focus-column-right = [ ]; + "Mod+WheelScrollLeft".focus-column-left = [ ]; + + "Mod+Shift+Prior".move-window-up-or-to-workspace-up = [ ]; + "Mod+Shift+Next".move-window-down-or-to-workspace-down = [ ]; + + "Mod+Ctrl+Left".focus-monitor-left = [ ]; + "Mod+Ctrl+Right".focus-monitor-right = [ ]; + + "Mod+Left".focus-column-left = [ ]; + "Mod+Right".focus-column-right = [ ]; + "Mod+Up".focus-window-or-workspace-up = [ ]; + "Mod+Down".focus-window-or-workspace-down = [ ]; + + "Mod+Shift+Left".move-column-left = [ ]; + "Mod+Shift+Right".move-column-right = [ ]; + "Mod+Shift+Up".move-window-up = [ ]; + "Mod+Shift+Down".move-window-down = [ ]; + + "Mod+R".switch-preset-column-width = [ ]; + "Mod+Shift+R".switch-preset-window-height = [ ]; + "Mod+Ctrl+R".reset-window-height = [ ]; + + "Mod+C".center-window = [ ]; + + "Mod+F".maximize-column = [ ]; + "Mod+Ctrl+F".expand-column-to-available-width = [ ]; + "Mod+Shift+F".fullscreen-window = [ ]; + "Mod+Alt+F".set-column-width = "50%"; + + "Mod+Minus".set-column-width = "-10%"; + "Mod+Equal".set-column-width = "+10%"; + "Mod+Shift+Minus".set-window-height = "-10%"; + "Mod+Shift+Equal".set-window-height = "+10%"; + + "Mod+Shift+Space".toggle-window-floating = [ ]; + + "Mod+T".toggle-column-tabbed-display = [ ]; + + "Mod+Print".spawn = [ + (lib.getExe pkgs.hyprpicker) + "--lowercase-hex" + "--autocopy" + ]; + + "Print".screenshot = [ ]; + "Shift+Print".screenshot-window = [ ]; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/programs/nixUtilities.nix b/nixosModules/zpha/programs/nixUtilities.nix @@ -0,0 +1,34 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.programs.nixUtilities.enable = lib.mkEnableOption "useful nix utilities"; + + config = lib.mkIf config.zpha.programs.nixUtilities.enable { + users.users.zaphyra.maid.packages = with pkgs; [ + unstable.npins + nixfmt-rfc-style + nixfmt-tree + nixd + statix + deadnix + nix-output-monitor + ]; + + zpha.programs.micro.lsp-servers.nix = { + command = lib.getExe pkgs.nixd; + options = { + config = builtins.toJSON { + formatting.command = [ (lib.getExe pkgs.nixfmt-rfc-style) ]; + }; + }; + }; + + }; + +}
diff --git a/nixosModules/zpha/programs/papers.nix b/nixosModules/zpha/programs/papers.nix @@ -0,0 +1,27 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.programs.papers.enable = lib.mkEnableOption "papers pdf viewer"; + + config = lib.mkIf config.zpha.programs.papers.enable { + users.users.zaphyra.maid = { + packages = [ pkgs.papers ]; + + xdg.mime-apps = { + defaultApplications = { + "application/pdf" = [ "org.gnome.Papers.desktop" ]; + }; + addedAssociations = { + "application/pdf" = [ "org.gnome.Papers.desktop" ]; + }; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/programs/pdfarranger.nix b/nixosModules/zpha/programs/pdfarranger.nix @@ -0,0 +1,22 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.programs.pdfarranger.enable = lib.mkEnableOption "pdfarranger"; + + config = lib.mkIf config.zpha.programs.pdfarranger.enable { + users.users.zaphyra.maid = { + packages = [ pkgs.pdfarranger ]; + + xdg.mime-apps.addedAssociations = { + "application/pdf" = [ "com.github.jeromerobert.pdfarranger.desktop" ]; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/programs/shaderbg.nix b/nixosModules/zpha/programs/shaderbg.nix @@ -0,0 +1,37 @@ +{ + resources, + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.programs.shaderbg.enable = lib.mkEnableOption "shaderbg"; + + config = lib.mkIf config.zpha.programs.shaderbg.enable { + users.users.zaphyra.maid = { + packages = [ pkgs.shaderbg ]; + + systemd.services.shaderbg = { + description = "A live wallpaper program for Sway and other compositors with wlr-layer-shell support."; + after = [ "niri-session.target" ]; + partOf = [ "niri-session.target" ]; + wantedBy = [ "niri-session.target" ]; + + unitConfig.ConditionEnvironment = "WAYLAND_DISPLAY"; + + serviceConfig = { + Restart = "on-failure"; + ExecStart = lib.escapeShellArgs [ + (lib.getExe pkgs.zpha.shaderbg) + "*" + resources.shaders.background1 + ]; + }; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/programs/shellUtilities.nix b/nixosModules/zpha/programs/shellUtilities.nix @@ -0,0 +1,94 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.programs.shellUtilities.enable = lib.mkEnableOption "useful shell-utilities"; + + config = lib.mkIf config.zpha.programs.shellUtilities.enable { + preservation.preserveAt."/persist".users.zaphyra.directories = [ + ".local/share/zoxide" + ".local/share/mcfly" + ]; + + users.users.zaphyra.maid = { + packages = with pkgs; [ + zoxide + eza + bat + ripgrep + fd + + pwgen + + (pkgs.writeShellScriptBin "use" '' + declare -a all + for p in "$@"; do + all+=("''${NIXPKGS_PATH}#$p") + done + eval nix shell ''${all[@]} + '') + + mcfly + mcfly-fzf + fzf + + wget + curl + rsync + + fx + jq + bc + file + + trash-cli + + zpha.nix-cleanup + ]; + + programs.fish = { + config = '' + zoxide init fish | source + mcfly init fish | source + mcfly-fzf init fish | source + ''; + + aliases = { + ".." = "cd .."; + "..." = "cd ../.."; + + cd = "z"; + cdi = "zi"; + + ls = "eza"; + ll = "eza -l"; + la = "eza -a"; + lla = "eza -la"; + tree = "eza --tree -a"; + + cat = "bat -pp"; + less = "bat"; + rm = "trash-put"; + + diff = "diff --color"; + ip = "ip --color=auto"; + + killall = "pkill"; + + grep = "rg"; + find = "fd"; + + wget = "wget --hsts-file=\"~/.local/state/wget-hsts\""; + + zzz = "sleep 1 && systemctl suspend"; + }; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/programs/ssh.nix b/nixosModules/zpha/programs/ssh.nix @@ -0,0 +1,37 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.programs.ssh.enable = lib.mkEnableOption "ssh"; + + config = lib.mkIf config.zpha.programs.ssh.enable { + preservation.preserveAt."/persist".users.zaphyra.directories = [ + { + directory = ".ssh"; + mode = "0700"; + } + ]; + + users.users.zaphyra.maid = { + packages = [ pkgs.openssh ]; + + file.home.".ssh/config".text = '' + Host * + Compression yes + ControlMaster yes + ControlPersist 2m + ServerAliveInterval 30 + + Host zaphyra-git + User git + Hostname git.zaphyra.eu + ''; + }; + }; + +}
diff --git a/nixosModules/zpha/programs/swaylock.nix b/nixosModules/zpha/programs/swaylock.nix @@ -0,0 +1,81 @@ +{ + resources, + config, + lib, + pkgs, + ... +}: +let + cfg = config.zpha.programs.swaylock; + +in +{ + + options.zpha.programs.swaylock = { + enable = lib.mkEnableOption "swaylock"; + settings = lib.mkOption { + default = { }; + type = + with lib.types; + attrsOf (oneOf [ + bool + float + int + path + str + ]); + }; + }; + + config = lib.mkIf cfg.enable { + zpha.programs.swaylock.settings = { + # clock = true; + + indicator = true; + ring-color = "4aa96c"; + show-failed-attempts = true; + fingerprint = false; + + command = "${lib.getExe pkgs.zpha.shaderbg} '*' --fps 10 ${resources.shaders.background1}"; + + # image = "/home/zaphyra/Pictures/Backgrounds/lock.png"; + # scaling = "fit"; + # color = "ffffaf"; + }; + + security.pam.services.swaylock = { }; + security.pam.services.swaylock-plugin = { }; + + services.systemd-lock-handler.enable = true; + + users.users.zaphyra.maid = { + packages = [ pkgs.pkgs.swaylock-plugin-fprintd ]; + + file.xdg_config = { + "swaylock/config".text = lib.concatStrings ( + lib.flip lib.mapAttrsToList cfg.settings ( + n: v: + if v == false then + "" + else + (if v == true then n else n + "=" + (if builtins.isPath v then "${v}" else toString v)) + "\n" + ) + ); + }; + + systemd.services.swaylock = { + wantedBy = [ "lock.target" ]; + partOf = [ "lock.target" ]; + after = [ "lock.target" ]; + environment = lib.mkForce { }; + unitConfig.OnSuccess = [ "unlock.target" ]; + serviceConfig = { + ExecStart = lib.getExe pkgs.swaylock-plugin-fprintd; + Restart = "on-failure"; + RestartSec = 0; + }; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/programs/thunderbird.nix b/nixosModules/zpha/programs/thunderbird.nix @@ -0,0 +1,40 @@ +{ + config, + lib, + pkgs, + npins, + ... +}: + +{ + + options.zpha.programs.thunderbird.enable = lib.mkEnableOption "thunderbird"; + + config = lib.mkIf config.zpha.programs.thunderbird.enable { + preservation.preserveAt."/persist".users.zaphyra.directories = [ + ".thunderbird" + ".cache/thunderbird" + ]; + + users.users.zaphyra.maid = { + packages = lib.singleton ( + pkgs.thunderbird.override (old: { + extraPrefsFiles = [ + (pkgs.writeText "firefox-autoconfig.js" '' + + '') + ]; + + nativeMessagingHosts = [ ]; + + extraPolicies = { + # disable tracking bullshit + DisableTelemetry = true; + }; + }) + ); + + }; + }; + +}
diff --git a/nixosModules/zpha/programs/typst.nix b/nixosModules/zpha/programs/typst.nix @@ -0,0 +1,25 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.programs.typst.enable = lib.mkEnableOption "typst (and utilities)"; + + config = lib.mkIf config.zpha.programs.typst.enable { + users.users.zaphyra.maid.packages = with pkgs; [ + typst + typst-live + typesetter + # typewriter + ]; + + zpha.programs.micro.lsp-servers = { + typ.command = lib.getExe pkgs.unstable.tinymist; + }; + }; + +}
diff --git a/nixosModules/zpha/programs/vibepanel.nix b/nixosModules/zpha/programs/vibepanel.nix @@ -0,0 +1,141 @@ +{ + lib, + pkgs, + config, + ... +}: + +{ + + options.zpha.programs.vibepanel.enable = lib.mkEnableOption "vibepanel"; + + config = lib.mkIf config.zpha.programs.vibepanel.enable { + users.users.zaphyra.maid = { + packages = [ pkgs.zpha.vibepanel ]; + + file.xdg_config = { + "vibepanel/config.toml".source = pkgs.writers.writeTOML "vibepanel.toml" { + advanced = { + compositor = "niri"; + }; + bar = { + position = "top"; + size = 32; + border_radius = 0; + background_color = "rgb(30, 40, 30)"; + background_opacity = 0.9; + }; + osd = { + enabled = true; + position = "top"; + }; + theme = { + mode = "dark"; + accent = "#4aa96c"; + ripple = false; + blur = true; + icons = { + theme = "gtk"; + weight = 400; + }; + }; + widgets = { + background_opacity = 1; + border_radius = 40; + left = [ + { + group = [ + "workspaces" + "taskbar" + ]; + } + ]; + center = [ + "clock" + "notifications" + ]; + right = [ + "media" + "tray" + { + group = [ + "quick_settings" + "battery" + ]; + } + ]; + quick_settings.updates = false; + taskbar.show_workspace_separator = false; + clock = { + format = "%d. %b %H:%M"; + on_click_right = + (lib.mkIf config.zpha.programs.gnome-calendar.enable (lib.getExe pkgs.gnome-calendar)).content; + }; + }; + }; + "vibepanel/style.css".text = '' + :root { + } + + .bar { + border-bottom: 2px solid rgba(59, 135, 86, 0.5); + } + + .widget, + .widget-item { + background-color: unset; + } + + .clock { + margin-right: -22px; + } + ''; + }; + + systemd.services.vibepanel = { + wantedBy = [ "niri-session.target" ]; + partOf = [ "niri-session.target" ]; + after = [ "niri-session.target" ]; + environment = lib.mkForce { }; + serviceConfig = { + ExecStart = lib.getExe pkgs.zpha.vibepanel; + Restart = "on-failure"; + }; + }; + }; + + zpha.programs.niri.settings.binds = { + "XF86AudioMute".spawn = [ + "vibepanel" + "volume" + "toggle-mute" + ]; + "XF86AudioLowerVolume".spawn = [ + "vibepanel" + "volume" + "dec" + "2" + ]; + "XF86AudioRaiseVolume".spawn = [ + "vibepanel" + "volume" + "inc" + "2" + ]; + "XF86MonBrightnessDown".spawn = [ + "vibepanel" + "brightness" + "dec" + "2" + ]; + "XF86MonBrightnessUp".spawn = [ + "vibepanel" + "brightness" + "inc" + "2" + ]; + }; + + }; + +}
diff --git a/nixosModules/zpha/programs/vicinae.nix b/nixosModules/zpha/programs/vicinae.nix @@ -0,0 +1,45 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.programs.vicinae.enable = lib.mkEnableOption "vicinae"; + + config = lib.mkIf config.zpha.programs.vicinae.enable { + preservation.preserveAt."/persist".users.zaphyra.directories = [ + ".config/vicinae" + ".local/share/vicinae" + ]; + + zpha.programs.niri.settings.binds = { + "Mod+Space".spawn = [ + (lib.getExe pkgs.vicinae) + "toggle" + ]; + }; + + users.users.zaphyra.maid = { + packages = [ pkgs.vicinae ]; + + systemd.services.vicinae = { + after = [ "niri-session.target" ]; + partOf = [ "niri-session.target" ]; + wantedBy = [ "niri-session.target" ]; + + environment = lib.mkForce { }; + + unitConfig.ConditionEnvironment = "WAYLAND_DISPLAY"; + + serviceConfig = { + Restart = "on-failure"; + ExecStart = "${lib.getExe pkgs.vicinae} server"; + }; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/programs/wpaperd.nix b/nixosModules/zpha/programs/wpaperd.nix @@ -0,0 +1,75 @@ +{ + lib, + pkgs, + config, + ... +}: + +{ + + options.zpha.programs.wpaperd.enable = lib.mkEnableOption "wpaperd"; + + config = lib.mkIf config.zpha.programs.wpaperd.enable { + users.users.zaphyra.maid = { + packages = [ pkgs.wpaperd ]; + + file.xdg_config = { + "wpaperd/wallpaper.toml".source = pkgs.writers.writeTOML "wpaperd-config.toml" { + default = { + duration = "30m"; + mode = "center"; + sorting = "ascending"; + transition.bounce = { }; + }; + + any.path = pkgs.symlinkJoin { + name = "backgrounds"; + paths = with pkgs.nixos-artwork.wallpapers; [ + binary-black + binary-blue + binary-red + + nineish + nineish-solarized-dark + nineish-dark-gray + nineish-catppuccin-mocha-alt + nineish-catppuccin-mocha + nineish-catppuccin-frappe-alt + nineish-catppuccin-frappe + nineish-catppuccin-macchiato-alt + nineish-catppuccin-macchiato + + simple-red + simple-dark-gray + simple-blue + + dracula + gradient-grey + ]; + stripPrefix = "/share/backgrounds/nixos"; + }; + }; + }; + + systemd.services.wpaperd = { + wantedBy = [ "niri-session.target" ]; + partOf = [ "niri-session.target" ]; + after = [ "niri-session.target" ]; + environment = lib.mkForce { }; + unitConfig.ConditionEnvironment = "WAYLAND_DISPLAY"; + serviceConfig = { + ExecStart = lib.getExe pkgs.wpaperd; + Restart = "on-failure"; + }; + }; + }; + + zpha.programs.niri.settings.binds = { + "Mod+Home".spawn = [ + (lib.getExe' pkgs.wpaperd "wpaperctl") + "next" + ]; + }; + }; + +}
diff --git a/nixosModules/zpha/programs/yt-dlp.nix b/nixosModules/zpha/programs/yt-dlp.nix @@ -0,0 +1,25 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.programs.yt-dlp.enable = lib.mkEnableOption "yt-dlp "; + + config = lib.mkIf config.zpha.programs.yt-dlp.enable { + users.users.zaphyra.maid = { + packages = with pkgs; [ + yt-dlp + (pkgs.writeShellScriptBin "youtube-dl" '' + exec ${pkgs.yt-dlp}/bin/yt-dlp --compat-options youtube-dl "$@" + '') + ]; + + programs.fish.aliases.yt-dlp-audio = "yt-dlp --format bestaudio -x --audio-format opus --add-metadata --embed-thumbnail"; + }; + }; + +}
diff --git a/nixosModules/zpha/programs/yubikey.nix b/nixosModules/zpha/programs/yubikey.nix @@ -0,0 +1,21 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.programs.yubikey.enable = lib.mkEnableOption "yubikey support"; + + config = lib.mkIf config.zpha.programs.yubikey.enable { + programs.yubikey-touch-detector.enable = true; + + users.users.zaphyra.maid.packages = with pkgs; [ + yubikey-manager + yubioath-flutter + ]; + }; + +}
diff --git a/nixosModules/zpha/services/avahi.nix b/nixosModules/zpha/services/avahi.nix @@ -0,0 +1,21 @@ +{ + config, + lib, + ... +}: + +{ + + options.zpha.services.avahi.enable = lib.mkEnableOption "avahi"; + + config = lib.mkIf config.zpha.services.avahi.enable { + services.avahi = { + enable = true; + nssmdns4 = true; + publish.enable = true; + publish.userServices = true; + }; + + }; + +}
diff --git a/nixosModules/zpha/services/batsignal.nix b/nixosModules/zpha/services/batsignal.nix @@ -0,0 +1,45 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.services.batsignal.enable = lib.mkEnableOption "batsignal"; + + config = lib.mkIf config.zpha.services.batsignal.enable { + users.users.zaphyra.maid = { + systemd.services.batsignal = { + description = "batsignal - battery monitor daemon"; + after = [ "niri-session.target" ]; + partOf = [ "niri-session.target" ]; + wantedBy = [ "niri-session.target" ]; + + serviceConfig = { + Restart = "on-failure"; + ExecStart = + let + args = [ + # expire notifications + "-e" + # show message when battery begins charging/discharging + "-p" + # minimum number of SECONDS to wait between battery checks + "-m10" + # bat levels + "-w20" + "-c10" + "-d5" + "-D" + "systemctl suspend" + ]; + in + "${lib.getExe pkgs.batsignal} ${lib.escapeShellArgs args}"; + }; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/services/dssd.nix b/nixosModules/zpha/services/dssd.nix @@ -0,0 +1,37 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.services.dssd.enable = lib.mkEnableOption "dssd"; + + config = lib.mkIf config.zpha.services.dssd.enable { + preservation.preserveAt."/persist".users.zaphyra.directories = [ + ".local/state/dssd" + ]; + + services.gnome.gnome-keyring.enable = lib.mkForce false; + + users.users.zaphyra.maid = { + dbus.packages = [ pkgs.zpha.dssd ]; + + systemd.services.dssd = { + unitConfig = { + Description = "Dead Simple Secret Daemon"; + Documentation = "https://github.com/ylxdzsw/dssd"; + }; + + serviceConfig = { + Type = "dbus"; + ExecStart = lib.getExe pkgs.zpha.dssd; + BusName = "org.freedesktop.secrets"; + }; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/services/keyd.nix b/nixosModules/zpha/services/keyd.nix @@ -0,0 +1,45 @@ +{ + config, + lib, + ... +}: + +{ + + options.zpha.services.keyd.enable = lib.mkEnableOption "keyd"; + + config = lib.mkIf config.zpha.services.keyd.enable { + users.groups.keyd = { }; + + systemd.services.keyd.serviceConfig = { + CapabilityBoundingSet = [ "CAP_SETGID" ]; + }; + + environment.etc."libinput/local-overrides.quirks".text = '' + [Serial Keyboards] + MatchUdevType=keyboard + MatchName=keyd virtual keyboard + AttrKeyboardIntegration=internal + ''; + + services.keyd = { + enable = true; + keyboards = { + any = { + ids = [ + "*" + ]; + settings.main = { + "leftcontrol" = "leftalt"; + "leftalt" = "leftcontrol"; + "s+d" = "oneshot(shift)"; + "f+j" = "space"; + "j+k" = "backspace"; + "k+l" = "enter"; + }; + }; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/services/knot.nix b/nixosModules/zpha/services/knot.nix @@ -0,0 +1,227 @@ +{ + machines, + nixosConfigurations, + machineConfig, + config, + lib, + pkgs, + ... +}: +let + inherit (lib) types; + cfg = config.zpha.services.knot; + + dnsServerAddresses = + isPrimary: + (lib.pipe nixosConfigurations [ + (lib.filterAttrs ( + _hostName: machineConfig: + let + cfgModule = machineConfig.config.zpha.services.knot; + in + cfgModule.enable && cfgModule.primary == isPrimary + )) + (lib.mapAttrsToList ( + hostName: _machineConfig: [ + (lib.mkIf (machines."${hostName}".networking.ip6Address != "") + machines."${hostName}".networking.ip6Address + ) + (lib.mkIf (machines."${hostName}".networking.ip4Address != "") + machines."${hostName}".networking.ip4Address + ) + ] + )) + lib.flatten + ]); + + dnsServerSecondaries = lib.pipe nixosConfigurations [ + (lib.filterAttrs ( + _hostName: machineConfig: + let + cfgModule = machineConfig.config.zpha.services.knot; + in + cfgModule.enable && !cfgModule.primary + )) + (lib.mapAttrs ( + hostName: _machineConfig: { + address = [ + (lib.mkIf (machines."${hostName}".networking.ip6Address != "") + machines."${hostName}".networking.ip6Address + ) + (lib.mkIf (machines."${hostName}".networking.ip4Address != "") + machines."${hostName}".networking.ip4Address + ) + ]; + } + )) + ]; + +in +{ + + options.zpha.services.knot = { + enable = lib.mkEnableOption "knot dns server"; + + primary = lib.mkOption { + type = types.bool; + default = false; + }; + + dataDir = lib.mkOption { + type = types.str; + default = "/var/lib/knot"; + }; + + keyFiles = lib.mkOption { + type = types.listOf types.path; + default = [ ]; + }; + + zones = lib.mkOption { + inherit ((pkgs.formats.yaml { })) type; + default = { }; + }; + + extraACL = lib.mkOption { + inherit ((pkgs.formats.yaml { })) type; + default = { }; + }; + }; + + config = lib.mkIf cfg.enable { + networking.firewall.allowedTCPPorts = [ 53 ]; + networking.firewall.allowedUDPPorts = [ 53 ]; + + users = { + users.knot.uid = 553; + groups.knot.gid = 553; + }; + + common.configure.persist.system.dirs = [ + { + directory = cfg.dataDir; + mode = "770"; + user = "knot"; + group = "knot"; + } + ]; + + systemd.tmpfiles.settings = { + knotDataDir."${cfg.dataDir}".d = { + group = "knot"; + user = "knot"; + mode = "770"; + age = "-"; + }; + }; + + services.knot = + let + primaryAddresses = dnsServerAddresses true; + secondaryAddresses = dnsServerAddresses false; + secondaries = dnsServerSecondaries; + + in + { + enable = true; + keyFiles = lib.mkIf (cfg.keyFiles != [ ]) cfg.keyFiles; + settings = { + log.syslog.any = "debug"; + + server.listen = [ + (lib.mkIf (machineConfig.networking.ip6Address != "") "${machineConfig.networking.ip6Address}@53") + (lib.mkIf (machineConfig.networking.ip4Address != "") "${machineConfig.networking.ip4Address}@53") + "::1@53" + "127.0.0.2@53" + ]; + + mod-rrl.default.rate-limit = 200; + mod-rrl.default.slip = 2; + + remote = { + primary.address = primaryAddresses; + } + // secondaries; + + acl = { + allowTransfer = lib.mkIf (secondaryAddresses != [ ]) { + address = secondaryAddresses; + action = "transfer"; + }; + + allowNotify.address = primaryAddresses; + allowNotify.action = "notify"; + } + // cfg.extraACL; + + template = + let + notify = { + acl = lib.mkIf (config.services.knot.settings.acl ? allowTransfer) "allowTransfer"; + notify = lib.mkIf (config.services.knot.settings.acl ? allowTransfer) ( + builtins.attrNames secondaries + ); + }; + + catalog = { + catalog-role = "member"; + catalog-zone = "catalog."; + }; + + in + { + + default = { + semantic-checks = true; + global-module = "mod-rrl/default"; + }; + + notifyZone = notify; + nixZone = notify // catalog; + + secondaryZone = { + master = "primary"; + acl = "allowNotify"; + + journal-content = "all"; + + zonefile-sync = -1; + zonefile-load = "none"; + }; + }; + + zone = + ( + if !cfg.primary then + { + "catalog.".catalog-role = "interpret"; + "catalog.".catalog-template = "secondaryZone"; + "catalog.".template = "secondaryZone"; + } + else + { + "catalog.".catalog-role = "generate"; + "catalog.".template = "notifyZone"; + } + ) + // (lib.mapAttrs ( + _name: zone: + zone + // { + template = "nixZone"; + acl = lib.mkIf (zone ? acl) ( + if (config.services.knot.settings.acl ? allowTransfer) then + lib.flatten [ + [ "allowTransfer" ] + zone.acl + ] + else + zone.acl + ); + } + ) cfg.zones); + }; + }; + }; + +}
diff --git a/nixosModules/zpha/services/knotACME.nix b/nixosModules/zpha/services/knotACME.nix @@ -0,0 +1,144 @@ +{ + nixosConfigurations, + pkgs, + lib, + config, + ... +}: +let + inherit (lib) types; + cfg = config.zpha.services.knotACME; + +in +{ + + options.zpha.services.knotACME = { + enable = lib.mkEnableOption ""; + nameServers = lib.mkOption { + type = types.listOf types.str; + }; + zone = lib.mkOption { + type = types.str; + }; + zones = lib.mkOption { + type = types.listOf types.str; + }; + keyFile = lib.mkOption { + type = types.str; + }; + }; + + config = lib.mkIf cfg.enable ( + let + generateACMERecord = recordName: ((builtins.hashString "sha1" recordName) + ".${cfg.zone}."); + + machinesWithACMERecords = lib.flip lib.filterAttrs nixosConfigurations ( + _machineName: machineConfig: machineConfig.config.security.acme.certs != { } + ); + + getAllDomainsPerMachine = + machineName: + (lib.pipe nixosConfigurations."${machineName}".config.security.acme.certs [ + (lib.mapAttrsToList (domain: cfg: [ domain ] ++ cfg.extraDomainNames)) + lib.flatten + ]); + + getACMERecordsPerMachine = + machineName: + lib.pipe machineName [ + getAllDomainsPerMachine + (builtins.map (recordName: (generateACMERecord recordName))) + ]; + + generateACMERecordsPerZone = + zoneName: + (lib.pipe machinesWithACMERecords [ + (lib.mapAttrsToList (hostName: _: (getAllDomainsPerMachine hostName))) + lib.flatten + (builtins.filter (lib.hasSuffix zoneName)) + (builtins.map (recordName: { + name = "_acme-challenge${ + if zoneName != recordName then "." else "" + }${lib.removeSuffix "${if zoneName != recordName then "." else ""}${zoneName}" recordName}"; + value = { + CNAME = [ (generateACMERecord recordName) ]; + }; + })) + builtins.listToAttrs + ]); + + in + { + dns.allZones = lib.pipe cfg.zones [ + (lib.map (element: lib.nameValuePair element { subdomains = generateACMERecordsPerZone element; })) + lib.listToAttrs + ]; + + systemd.services.knot = + let + acmeZoneFile = pkgs.writeTextFile { + name = "${cfg.zone}.zone"; + text = pkgs.dnsNix.types.zoneToString cfg.zone ( + pkgs.dnsNix.evalZone cfg.zone { + NS = cfg.nameServers; + SOA = { + nameServer = lib.elemAt cfg.nameServers 0; + adminEmail = "dns@${cfg.zone}"; # Email address with a real `@`! + serial = 0; + }; + } + ); + }; + in + { + reloadTriggers = [ "${config.zpha.services.knot.dataDir}/acme.zone" ]; + serviceConfig = { + ExecStartPre = [ + (pkgs.writeShellScript "knot-acmeZone-preStart" '' + set -eou pipefail + cp --dereference ${acmeZoneFile} ${config.zpha.services.knot.dataDir}/acme.zone + chmod -R 770 ${config.zpha.services.knot.dataDir}/acme.zone + '') + ]; + ExecReload = lib.mkForce ( + pkgs.writeShellScript "knot-reload" '' + set -eou pipefail + cp --dereference ${acmeZoneFile} ${config.zpha.services.knot.dataDir}/acme.zone + chmod -R 770 ${config.zpha.services.knot.dataDir}/acme.zone + ${config.services.knot.package}/bin/knotc reload + '' + ); + }; + }; + + zpha.services.knot = { + keyFiles = [ cfg.keyFile ]; + zones = { + "${cfg.zone}" = { + file = "${config.zpha.services.knot.dataDir}/acme.zone"; + + zonefile-sync = 0; + zonefile-load = "difference-no-serial"; + + journal-content = "all"; + + acl = lib.mkIf ((lib.attrNames machinesWithACMERecords) != [ ]) ( + lib.mapAttrsToList (hostName: _: "acme-nix-${hostName}") machinesWithACMERecords + ); + }; + }; + extraACL = lib.mapAttrs' (hostName: _: { + name = "acme-nix-${hostName}"; + value = { + key = [ "acme-nix-${hostName}" ]; + action = "update"; + update-owner = "name"; + update-owner-match = "equal"; + update-owner-name = getACMERecordsPerMachine hostName; + }; + }) machinesWithACMERecords; + }; + } + ); + +}
diff --git a/nixosModules/zpha/services/oniri.nix b/nixosModules/zpha/services/oniri.nix @@ -0,0 +1,26 @@ +{ + lib, + pkgs, + config, + ... +}: + +{ + + options.zpha.services.oniri.enable = lib.mkEnableOption "oniri"; + + config = lib.mkIf config.zpha.services.oniri.enable { + users.users.zaphyra.maid = { + systemd.services.oniri = { + wantedBy = [ "niri-session.target" ]; + partOf = [ "niri-session.target" ]; + after = [ "niri-session.target" ]; + serviceConfig = { + ExecStart = lib.getExe pkgs.zpha.oniri; + Restart = "on-failure"; + }; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/services/pipewire.nix b/nixosModules/zpha/services/pipewire.nix @@ -0,0 +1,76 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + inherit (lib) types; + cfg = config.zpha.services.pipewire; + +in +{ + + options.zpha.services.pipewire.enable = lib.mkEnableOption "pipewire"; + + config = lib.mkIf cfg.enable { + users.users.zaphyra.maid = { + packages = with pkgs; [ + pwvucontrol + unstable.crosspipe + #pipemixer + unstable.wiremix + ]; + + file.xdg_config."pipewire/pipewire.conf.d/10-chloe-raop.conf".text = + let + raopSink = ipAddr: name: { + name = "libpipewire-module-raop-sink"; + args = { + "raop.ip" = ipAddr; + "raop.port" = 7000; + "raop.name" = name; + "raop.encryption.type" = "auth_setup"; + }; + }; + + in + builtins.toJSON { + "context.modules" = [ + { name = "libpipewire-module-zeroconf-discover"; } + { + name = "libpipewire-module-raop-discover"; + args."roap.discover-local" = true; + } + # (raopSink "192.168.2.63" "Living Room") + # (raopSink "192.168.2.57" "Bed Room") + # (raopSink "192.168.2.54" "Datacenter") + # (raopSink "192.168.2.80" "Kitchen") + # { + # name = "libpipewire-module-combine-stream"; + # args = { + # "node.name" = "raop_multi_room"; + # "node.description" = "Multi-Room"; + # "combine.mode" = "sink"; + # "combine.latency-compensate" = false; + # "combine.on-demand-streams" = true; + # "stream.rules" = [ + # { + # actions."create-stream" = { }; + # matches = [ + # { + # "media.class" = "Audio/Sink"; + # "node.name" = "~raop_sink.*"; + # } + # ]; + # } + # ]; + # }; + # } + ]; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/services/wlsunset.nix b/nixosModules/zpha/services/wlsunset.nix @@ -0,0 +1,39 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.services.wlsunset.enable = lib.mkEnableOption "batsignal"; + + config = lib.mkIf config.zpha.services.wlsunset.enable { + users.users.zaphyra.maid = { + systemd.services.batsignal = { + description = "Day/night gamma adjustments for Wayland compositors."; + after = [ "niri-session.target" ]; + partOf = [ "niri-session.target" ]; + wantedBy = [ "niri-session.target" ]; + + unitConfig.ConditionEnvironment = "WAYLAND_DISPLAY"; + + serviceConfig = { + Restart = "on-failure"; + ExecStart = + let + args = lib.cli.toCommandLineShellGNU { } { + t = 3000; + T = 4500; + l = "8.26"; + L = "49.01"; + }; + in + "${lib.getExe pkgs.wlsunset} ${args}"; + }; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/websites/bikemap.zaphyra.eu.nix b/nixosModules/zpha/websites/bikemap.zaphyra.eu.nix @@ -0,0 +1,124 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.websites."bikemap.zaphyra.eu".enable = lib.mkEnableOption ""; + + config = lib.mkIf config.zpha.websites."bikemap.zaphyra.eu".enable ( + let + deployScript = pkgs.writeShellScript "deployBikemap" '' + systemctl start deployBikemap; + systemctl status deployBikemap; + ''; + + in + { + assertions = lib.singleton { + assertion = config.common.services.gitolite.enable; + message = "The option 'common.services.gitolite.enable' must be enabled in order to use this module."; + }; + + dns.zones."zaphyra.eu".subdomains."bikemap".CNAME = lib.singleton "${config.networking.fqdn}."; + + common.configure.persist.system.dirs = lib.singleton { + directory = "/var/lib/bikemap"; + mode = "0755"; + user = "bikemap"; + inherit (config.common.services.gitolite) group; + }; + + users.users."bikemap" = { + isSystemUser = true; + inherit (config.common.services.gitolite) group; + createHome = true; + homeMode = "755"; + home = "/var/lib/bikemap"; + }; + + security.sudo.extraRules = [ + { + users = [ "git" ]; + commands = [ + { + command = "${deployScript}"; + options = [ + "SETENV" + "NOPASSWD" + ]; + } + ]; + } + ]; + + systemd.services.deployBikemap = { + script = '' + # strict mode + set -euo pipefail + IFS=$'\n\t' + + TMP_DIR=$(mktemp -d) + trap "{ rm -rf "$TMP_DIR"; }" SIGINT SIGTERM ERR EXIT + + ${pkgs.git}/bin/git config --global --add safe.directory ${config.common.services.gitolite.dataDir}/repositories/biketracks.git + ${pkgs.git}/bin/git clone ${config.common.services.gitolite.dataDir}/repositories/biketracks.git $TMP_DIR/tracks + + mkdir $TMP_DIR/tiles + + ${pkgs.zpha.generateTilesFromGPX}/bin/generateTilesFromGPX $TMP_DIR/tracks $TMP_DIR/tiles + + rm -rf ~/*; + + ln -sf ${pkgs.zpha.gpx-map}/index.html ~/index.html + ln -sf ${pkgs.zpha.gpx-map}/bundle.js ~/bundle.js + mv $TMP_DIR/tiles ~/tiles; + echo "{\"lastUpdated\":\"$(date +"%Y-%m-%d %H:%M")\"}" > ~/lastUpdated.json + ''; + + serviceConfig = { + Type = "oneshot"; + + User = "bikemap"; + Group = config.common.services.gitolite.group; + + WorkingDirectory = "~"; + StateDirectory = "bikemap"; + StateDirectoryMode = "755"; + + NoNewPrivileges = true; + PrivateTmp = true; + PrivateDevices = true; + + RestrictAddressFamilies = "none"; + RestrictNamespaces = true; + RestrictRealtime = true; + + ProtectSystem = "full"; + ProtectControlGroups = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + + DevicePolicy = "closed"; + LockPersonality = true; + }; + }; + + common.services.gitolite.commonHooks.post-receive = '' + #deploy bikemap + [ "$GL_REPO" == "biketracks" ] && sudo ${deployScript} + ''; + + services.nginx.virtualHosts."bikemap.zaphyra.eu" = { + useACMEHost = "${config.networking.fqdn}"; + forceSSL = true; + kTLS = true; + root = "/var/lib/bikemap/"; + }; + } + ); + +}
diff --git a/nixosModules/zpha/websites/continuwuity.zaphyra.eu.nix b/nixosModules/zpha/websites/continuwuity.zaphyra.eu.nix @@ -0,0 +1,122 @@ +{ + npins, + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.websites."continuwuity.zaphyra.eu".enable = lib.mkEnableOption ""; + + config = lib.mkIf config.zpha.websites."continuwuity.zaphyra.eu".enable { + dns.zones."zaphyra.eu".subdomains."continuwuity".CNAME = [ "${config.networking.fqdn}." ]; + + sops.secrets = { + "restic/continuwuity/repositoryPassword" = { }; + "restic/continuwuity/sshPrivateKey" = { }; + }; + + common = { + configure.persist.system.dirs = [ + "/var/lib/private/continuwuity" + ]; + + services.resticBackup.continuwuity = { + enable = true; + targets = [ + "restic-target.fc9f.de" + "isodon.fc9f.de" + ]; + sshKeyFile = config.sops.secrets."restic/continuwuity/sshPrivateKey".path; + passwordFile = config.sops.secrets."restic/continuwuity/repositoryPassword".path; + paths = [ "/var/lib/private/continuwuity" ]; + runBeforeBackup = '' + systemctl stop continuwuity + ${lib.getExe pkgs.rsync} -a --exclude /var/lib/private/continuwuity/media /var/lib/private/continuwuity /tmp/continuwuity-database + systemctl start continuwuity + ''; + }; + }; + + services.matrix-continuwuity = { + enable = true; + package = + # (npins.continuwuity).outputs.packages.${pkgs.stdenv.hostPlatform.system}.default.overrideAttrs + pkgs.unstable.matrix-continuwuity; + settings = { + global = { + address = [ "::1" ]; + trusted_servers = [ + "matrix.org" + "tchncs.de" + ]; + server_name = "zaphyra.eu"; + allow_registration = false; + log_to_journald = true; + }; + }; + }; + + services.nginx.virtualHosts = + let + matrixServerConfig = { + "m.server" = "continuwuity.zaphyra.eu:443"; + }; + matrixClientConfig = { + "m.homeserver".base_url = "https://continuwuity.zaphyra.eu/"; + "org.matrix.msc4143.rtc_foci" = [ + { + type = "livekit"; + livekit_service_url = "https://livekit.rtc.matrix.nwex.de"; + } + ]; + }; + in + { + "${config.services.matrix-continuwuity.settings.global.server_name}" = { + locations = { + "= /.well-known/matrix/server".extraConfig = '' + add_header Content-Type application/json; + add_header "Access-Control-Allow-Origin" "*"; + add_header "Access-Control-Allow-Methods" "GET, POST, PUT, DELETE, OPTIONS"; + add_header "Access-Control-Allow-Headers" "Origin, X-Requested-With, Content-Type, Accept, Authorization"; + return 200 '${builtins.toJSON matrixServerConfig}'; + ''; + "= /.well-known/matrix/client".extraConfig = '' + add_header Content-Type application/json; + add_header "Access-Control-Allow-Origin" "*"; + add_header "Access-Control-Allow-Methods" "GET, POST, PUT, DELETE, OPTIONS"; + add_header "Access-Control-Allow-Headers" "Origin, X-Requested-With, Content-Type, Accept, Authorization"; + return 200 '${builtins.toJSON matrixClientConfig}'; + ''; + "/_matrix" = { + proxyPass = "http://[::1]:${toString config.services.matrix-continuwuity.settings.global.port}"; + proxyWebsockets = true; + }; + }; + }; + "continuwuity.zaphyra.eu" = { + useACMEHost = "${config.networking.fqdn}"; + forceSSL = true; + kTLS = true; + locations = { + "/_matrix" = { + proxyPass = "http://[::1]:${toString config.services.matrix-continuwuity.settings.global.port}"; + proxyWebsockets = true; + }; + "/".root = pkgs.cinny.override { + conf = { + defaultHomeserver = 0; + homeserverList = [ config.services.matrix-continuwuity.settings.global.server_name ]; + hashRouter.enabled = true; + allowCustomHomesevrers = false; + }; + }; + }; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/websites/ctu.cx.nix b/nixosModules/zpha/websites/ctu.cx.nix @@ -0,0 +1,22 @@ +{ + machineConfig, + config, + lib, + ... +}: + +{ + + options.zpha.websites."ctu.cx".enable = lib.mkEnableOption ""; + + config = lib.mkIf config.zpha.websites."ctu.cx".enable { + dns.zones."ctu.cx".AAAA = lib.singleton machineConfig.networking.ip6Address; + + services.nginx.virtualHosts."ctu.cx" = { + useACMEHost = "${config.networking.fqdn}"; + forceSSL = true; + kTLS = true; + }; + }; + +}
diff --git a/nixosModules/zpha/websites/dav.zaphyra.eu.nix b/nixosModules/zpha/websites/dav.zaphyra.eu.nix @@ -0,0 +1,73 @@ +{ + config, + lib, + ... +}: + +{ + + options.zpha.websites."dav.zaphyra.eu".enable = lib.mkEnableOption ""; + + config = lib.mkIf config.zpha.websites."dav.zaphyra.eu".enable { + dns.zones."zaphyra.eu".subdomains."dav".CNAME = [ "${config.networking.fqdn}." ]; + + sops.secrets = { + "restic/radicale/repositoryPassword" = { }; + "restic/radicale/sshPrivateKey" = { }; + radicaleUsers = { + owner = config.systemd.services.radicale.serviceConfig.User; + restartUnits = [ "radicale.service" ]; + }; + }; + + users = { + users.radicale.uid = 234; + groups.radicale.gid = 234; + }; + + common = { + configure.persist.system.dirs = lib.singleton { + mode = "700"; + user = config.systemd.services.radicale.serviceConfig.User; + group = config.systemd.services.radicale.serviceConfig.Group; + directory = config.services.radicale.settings.storage.filesystem_folder; + }; + + services.resticBackup.radicale = { + user = config.systemd.services.radicale.serviceConfig.User; + enable = true; + targets = [ + "restic-target.fc9f.de" + "isodon.fc9f.de" + ]; + sshKeyFile = config.sops.secrets."restic/radicale/sshPrivateKey".path; + passwordFile = config.sops.secrets."restic/radicale/repositoryPassword".path; + paths = [ config.services.radicale.settings.storage.filesystem_folder ]; + }; + }; + + services = { + radicale = { + enable = true; + settings = { + server.hosts = [ "[::1]:5232" ]; + web.type = "internal"; + storage.filesystem_folder = "/var/lib/radicale"; + headers.Access-Control-Allow-Origin = "*"; + auth = { + type = "htpasswd"; + htpasswd_filename = config.sops.secrets.radicaleUsers.path; + htpasswd_encryption = "plain"; + }; + }; + }; + nginx.virtualHosts."dav.zaphyra.eu" = { + useACMEHost = config.networking.fqdn; + forceSSL = true; + kTLS = true; + locations."/".proxyPass = "http://${lib.elemAt config.services.radicale.settings.server.hosts 0}/"; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/websites/ente.zaphyra.eu.nix b/nixosModules/zpha/websites/ente.zaphyra.eu.nix @@ -0,0 +1,133 @@ +{ + machineConfig, + config, + lib, + ... +}: + +let + subdomains = [ + "accounts" + "api" + "albums" + "cast" + "photos" + ]; + +in +{ + + options.zpha.websites."ente.zaphyra.eu".enable = lib.mkEnableOption ""; + + config = lib.mkIf config.zpha.websites."ente.zaphyra.eu".enable { + dns.zones = { + "zaphyra.eu".subdomains = + (lib.genAttrs' subdomains ( + name: + lib.nameValuePair "${name}.ente" { + CNAME = [ "ente.zaphyra.eu." ]; + } + )) + // { + "ente".AAAA = [ machineConfig.networking.ip6Address ]; + }; + "fc9f.de".subdomains."s3.${config.networking.hostName}".CNAME = [ + "${config.networking.fqdn}." + ]; + }; + + sops.secrets = { + "environments/ente" = { + owner = "ente"; + group = "ente"; + }; + "environments/minio" = { + owner = "minio"; + group = "minio"; + }; + }; + + common.configure.persist.system.dirs = [ + { + user = "minio"; + group = "minio"; + directory = "/var/lib/minio"; + mode = "0700"; + } + { + user = "postgres"; + group = "postgres"; + directory = "/var/lib/postgresql"; + mode = "0700"; + } + { + inherit (config.services.ente.api) user group; + directory = "/var/lib/ente"; + mode = "0700"; + } + ]; + + systemd.services.ente.serviceConfig.EnvironmentFile = config.sops.secrets."environments/ente".path; + + services = { + postgresql.enable = true; + + minio = { + enable = true; + rootCredentialsFile = config.sops.secrets."environments/minio".path; + browser = false; + listenAddress = "[::1]:9000"; + }; + + ente = { + web = { + enable = true; + domains = lib.genAttrs subdomains (name: "${name}.ente.zaphyra.eu"); + }; + api = { + enable = true; + enableLocalDB = true; + nginx.enable = true; + domain = "api.ente.zaphyra.eu"; + settings = { + internal.admin = 1580559962386438; + s3.b2-eu-cen = { + use_path_style_urls = true; + are_local_buckets = true; + endpoint = "https://s3.${config.networking.fqdn}/"; + region = "us-east-1"; + bucket = "ente"; + }; + }; + }; + }; + + nginx.virtualHosts = + (lib.genAttrs' (lib.filter (name: name != "albums") subdomains) ( + name: + lib.nameValuePair "${name}.ente.zaphyra.eu" { + serverAliases = lib.mkIf (name == "photos") [ + "albums.ente.zaphyra.eu" + ]; + useACMEHost = config.networking.fqdn; + forceSSL = true; + kTLS = true; + } + )) + // { + "s3.${config.networking.fqdn}" = { + useACMEHost = config.networking.fqdn; + forceSSL = true; + kTLS = true; + locations."/" = { + proxyPass = "http://[::1]:9000"; + extraConfig = '' + client_max_body_size 100m; + ''; + }; + }; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/websites/fedi.ctu.cx.nix b/nixosModules/zpha/websites/fedi.ctu.cx.nix @@ -0,0 +1,187 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.websites."fedi.ctu.cx".enable = lib.mkEnableOption ""; + + config = lib.mkIf config.zpha.websites."fedi.ctu.cx".enable { + dns.zones."ctu.cx".subdomains."fedi".CNAME = [ + "${config.networking.fqdn}." + ]; + + sops.secrets = { + "restic/gotosocial/repositoryPassword" = { }; + "restic/gotosocial/sshPrivateKey" = { }; + }; + + systemd.tmpfiles.settings.gotosocial = { + "/var/lib/gotosocial/storage".d = { + inherit (config.common.services.gotosocial) user; + inherit (config.common.services.gotosocial) group; + mode = "750"; + age = "-"; + }; + }; + + common = { + configure.persist.system.dirs = [ + { + inherit (config.common.services.gotosocial) user group; + mode = "0755"; + directory = config.common.services.gotosocial.stateDir; + } + ]; + + services.resticBackup.gotosocial = { + enable = true; + inherit (config.common.services.gotosocial) user; + targets = [ + "restic-target.fc9f.de" + "isodon.fc9f.de" + ]; + sshKeyFile = config.sops.secrets."restic/gotosocial/sshPrivateKey".path; + passwordFile = config.sops.secrets."restic/gotosocial/repositoryPassword".path; + sqliteDatabases = [ + (lib.mkIf ( + config.common.services.gotosocial.settings.db-type == "sqlite" + ) config.common.services.gotosocial.settings.db-address) + ]; + paths = [ + (lib.mkIf ( + config.common.services.gotosocial.settings.storage-backend == "local" + ) config.common.services.gotosocial.settings.storage-local-base-path) + "${config.common.services.gotosocial.stateDir}/backup.json" + ]; + runBeforeBackup = '' + ${config.common.services.gotosocial.package}/bin/gotosocial --config-path /etc/gotosocial.yaml admin export --path ${config.common.services.gotosocial.stateDir}/backup.json + ''; + }; + }; + + common.services.gotosocial = { + enable = true; + package = pkgs.zpha.gotosocial-unstable; + inherit (config.services.nginx) group; + settings = { + protocol = "https"; + + bind-address = "::1"; + port = 8085; + + trusted-proxies = [ + "::1/128" + "172.17.0.0/24" + ]; + + storage-backend = lib.mkDefault "local"; + storage-local-base-path = "${config.common.services.gotosocial.stateDir}/storage"; + + host = "fedi.ctu.cx"; + account-domain = "ctu.cx"; + + landing-page-user = "katja"; + + accounts-allow-custom-css = true; + accounts-registration-open = false; + + instance-expose-peers = true; + instance-expose-suspended = true; + instance-expose-suspended-web = true; + + instance-languages = [ + "de" + "en-us" + ]; + + media-local-max-size = "50MiB"; + media-remote-max-size = "50MiB"; + + media-remote-cache-duration = "3 days"; + media-cleanup-cron = "30 23 * * *"; + + statuses-cleanup-remote-older-than = "1 year"; + }; + }; + + services.nginx = { + appendHttpConfig = '' + proxy_cache_path /var/cache/nginx keys_zone=gotosocial_ap_public_responses:10m inactive=1w; + ''; + virtualHosts = { + "${config.common.services.gotosocial.settings.host}" = { + useACMEHost = lib.mkDefault config.networking.fqdn; + forceSSL = lib.mkDefault true; + kTLS = lib.mkDefault true; + locations = { + "/" = { + proxyPass = "http://[${toString config.common.services.gotosocial.settings.bind-address}]:${toString config.common.services.gotosocial.settings.port}"; + proxyWebsockets = true; + }; + + "~ /.well-known/(webfinger|host-meta)$" = { + proxyPass = "http://[${toString config.common.services.gotosocial.settings.bind-address}]:${toString config.common.services.gotosocial.settings.port}"; + extraConfig = '' + proxy_cache gotosocial_ap_public_responses; + proxy_cache_background_update on; + proxy_cache_key $scheme://$host$uri$is_args$query_string; + proxy_cache_valid 200 10m; + proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504 http_429; + proxy_cache_lock on; + add_header X-Cache-Status $upstream_cache_status; + ''; + }; + + "~ ^\/users\/(?:[a-z0-9_\.]+)\/main-key$" = { + proxyPass = "http://[${toString config.common.services.gotosocial.settings.bind-address}]:${toString config.common.services.gotosocial.settings.port}"; + extraConfig = '' + proxy_cache gotosocial_ap_public_responses; + proxy_cache_background_update on; + proxy_cache_key $scheme://$host$uri; + proxy_cache_valid 200 604800s; + proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504 http_429; + proxy_cache_lock on; + + add_header X-Cache-Status $upstream_cache_status; + ''; + }; + + "/assets/".extraConfig = '' + alias ${config.common.services.gotosocial.package}/share/gotosocial/web/assets/; + autoindex off; + expires max; + add_header Cache-Control "public, immutable"; + ''; + }; + }; + } + // ( + if + ( + config.common.services.gotosocial.settings.account-domain + != config.common.services.gotosocial.settings.host + ) + then + { + "${config.common.services.gotosocial.settings.account-domain}" = { + locations = { + "= /.well-known/host-meta".extraConfig = + "return 301 https://${config.common.services.gotosocial.settings.host}$request_uri;"; + "= /.well-known/webfinger".extraConfig = + "return 301 https://${config.common.services.gotosocial.settings.host}$request_uri;"; + "= /.well-known/nodeinfo".extraConfig = + "return 301 https://${config.common.services.gotosocial.settings.host}$request_uri;"; + }; + }; + } + else + { } + ); + }; + }; + +}
diff --git a/nixosModules/zpha/websites/fedi.home.ctu.cx.nix b/nixosModules/zpha/websites/fedi.home.ctu.cx.nix @@ -0,0 +1,185 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.websites."fedi.home.ctu.cx".enable = lib.mkEnableOption ""; + + config = lib.mkIf config.zpha.websites."fedi.home.ctu.cx".enable { + dns.zones."ctu.cx".subdomains."fedi.home".CNAME = [ + "${config.networking.fqdn}." + ]; + + sops.secrets = { + "restic/gotosocial/repositoryPassword" = { }; + "restic/gotosocial/sshPrivateKey" = { }; + }; + + systemd.tmpfiles.settings.gotosocial = { + "/var/lib/gotosocial/storage".d = { + inherit (config.common.services.gotosocial) user; + inherit (config.common.services.gotosocial) group; + mode = "750"; + age = "-"; + }; + }; + + common = { + configure.persist.system.dirs = lib.singleton { + inherit (config.common.services.gotosocial) user group; + directory = config.common.services.gotosocial.stateDir; + mode = "0755"; + }; + + services.resticBackup.gotosocial = { + enable = true; + inherit (config.common.services.gotosocial) user; + targets = [ + "restic-target.fc9f.de" + "isodon.fc9f.de" + ]; + sshKeyFile = config.sops.secrets."restic/gotosocial/sshPrivateKey".path; + passwordFile = config.sops.secrets."restic/gotosocial/repositoryPassword".path; + sqliteDatabases = [ + (lib.mkIf ( + config.common.services.gotosocial.settings.db-type == "sqlite" + ) config.common.services.gotosocial.settings.db-address) + ]; + paths = [ + (lib.mkIf ( + config.common.services.gotosocial.settings.storage-backend == "local" + ) config.common.services.gotosocial.settings.storage-local-base-path) + "${config.common.services.gotosocial.stateDir}/backup.json" + ]; + runBeforeBackup = '' + ${config.common.services.gotosocial.package}/bin/gotosocial --config-path /etc/gotosocial.yaml admin export --path ${config.common.services.gotosocial.stateDir}/backup.json + ''; + }; + }; + + common.services.gotosocial = { + enable = true; + package = pkgs.zpha.gotosocial-unstable; + inherit (config.services.nginx) group; + settings = { + protocol = "https"; + + bind-address = "::1"; + port = 8085; + + trusted-proxies = [ + "::1/128" + "172.17.0.0/24" + ]; + + storage-backend = lib.mkDefault "local"; + storage-local-base-path = "${config.common.services.gotosocial.stateDir}/storage"; + + host = "fedi.home.ctu.cx"; + account-domain = "fedi.home.ctu.cx"; + + landing-page-user = "leah"; + + accounts-allow-custom-css = true; + accounts-registration-open = false; + + instance-expose-peers = true; + instance-expose-suspended = true; + instance-expose-suspended-web = true; + + instance-languages = [ + "de" + "en-us" + ]; + + media-local-max-size = "50MiB"; + media-remote-max-size = "50MiB"; + + media-remote-cache-duration = "3 days"; + media-cleanup-cron = "30 23 * * *"; + + statuses-cleanup-remote-older-than = "1 year"; + }; + }; + + services.nginx = { + appendHttpConfig = '' + proxy_cache_path /var/cache/nginx keys_zone=gotosocial_ap_public_responses:10m inactive=1w; + ''; + virtualHosts = { + "${config.common.services.gotosocial.settings.host}" = { + useACMEHost = lib.mkDefault config.networking.fqdn; + forceSSL = lib.mkDefault true; + kTLS = lib.mkDefault true; + locations = { + "/" = { + proxyPass = "http://[${toString config.common.services.gotosocial.settings.bind-address}]:${toString config.common.services.gotosocial.settings.port}"; + proxyWebsockets = true; + }; + + "~ /.well-known/(webfinger|host-meta)$" = { + proxyPass = "http://[${toString config.common.services.gotosocial.settings.bind-address}]:${toString config.common.services.gotosocial.settings.port}"; + extraConfig = '' + proxy_cache gotosocial_ap_public_responses; + proxy_cache_background_update on; + proxy_cache_key $scheme://$host$uri$is_args$query_string; + proxy_cache_valid 200 10m; + proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504 http_429; + proxy_cache_lock on; + add_header X-Cache-Status $upstream_cache_status; + ''; + }; + + "~ ^\/users\/(?:[a-z0-9_\.]+)\/main-key$" = { + proxyPass = "http://[${toString config.common.services.gotosocial.settings.bind-address}]:${toString config.common.services.gotosocial.settings.port}"; + extraConfig = '' + proxy_cache gotosocial_ap_public_responses; + proxy_cache_background_update on; + proxy_cache_key $scheme://$host$uri; + proxy_cache_valid 200 604800s; + proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504 http_429; + proxy_cache_lock on; + + add_header X-Cache-Status $upstream_cache_status; + ''; + }; + + "/assets/".extraConfig = '' + alias ${config.common.services.gotosocial.package}/share/gotosocial/web/assets/; + autoindex off; + expires max; + add_header Cache-Control "public, immutable"; + ''; + }; + }; + } + // ( + if + ( + config.common.services.gotosocial.settings.account-domain + != config.common.services.gotosocial.settings.host + ) + then + { + "${config.common.services.gotosocial.settings.account-domain}" = { + locations = { + "= /.well-known/host-meta".extraConfig = + "return 301 https://${config.common.services.gotosocial.settings.host}$request_uri;"; + "= /.well-known/webfinger".extraConfig = + "return 301 https://${config.common.services.gotosocial.settings.host}$request_uri;"; + "= /.well-known/nodeinfo".extraConfig = + "return 301 https://${config.common.services.gotosocial.settings.host}$request_uri;"; + }; + }; + } + else + { } + ); + }; + }; + +}
diff --git a/nixosModules/zpha/websites/flauschehorn.zaphyra.eu.nix b/nixosModules/zpha/websites/flauschehorn.zaphyra.eu.nix @@ -0,0 +1,94 @@ +{ + config, + pkgs, + lib, + ... +}: + +{ + + options.zpha.websites."flauschehorn.zaphyra.eu".enable = lib.mkEnableOption ""; + + config = lib.mkIf config.zpha.websites."flauschehorn.zaphyra.eu".enable { + dns.zones."zaphyra.eu".subdomains."flauschehorn".CNAME = [ + "${config.networking.fqdn}." + ]; + + # required because this subdomain is still set in the flauschehorn.sexy-zone + dns.zones."ctu.cx".subdomains."63bc37c61bda3c1f4fa1f270f8890c7f89c24353.acme".CNAME = [ + "63bc37c61bda3c1f4fa1f270f8890c7f89c24353.acme.fc9f.de." + ]; + + common.configure.persist.system.dirs = [ + "/var/lib/private/flauschehorn" + ]; + + systemd.services.flauschehornFetcher = { + environment.DB_PATH = "/var/lib/flauschehorn/db.sqlite"; + startAt = "*-*-* 3:00:00"; + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; + serviceConfig = { + Type = "oneshot"; + + ExecStart = "${pkgs.zpha.flauschehorn-sexy}/bin/mastofetch"; + + DynamicUser = true; + User = "flauschehorn"; + Group = "flauschehorn"; + + StateDirectory = "flauschehorn"; + StateDirectoryMode = "755"; + UMask = "022"; + + NoNewPrivileges = true; + PrivateTmp = true; + PrivateDevices = true; + + RestrictAddressFamilies = "AF_INET AF_INET6"; + RestrictNamespaces = true; + RestrictRealtime = true; + + ProtectSystem = "full"; + ProtectControlGroups = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + + DevicePolicy = "closed"; + LockPersonality = true; + }; + }; + + systemd.services.fcgiwrap-flauschehorn.serviceConfig = { + DynamicUser = true; + User = "flauschehorn"; + Group = "flauschehorn"; + + StateDirectory = "flauschehorn"; + StateDirectoryMode = "555"; + }; + + services.fcgiwrap.instances.flauschehorn = { + socket.user = config.services.nginx.user; + socket.group = config.services.nginx.group; + }; + + services.nginx = { + enable = true; + virtualHosts."flauschehorn.zaphyra.eu" = { + serverAliases = [ "flauschehorn.sexy" ]; + useACMEHost = "${config.networking.fqdn}"; + forceSSL = true; + kTLS = true; + locations."/".extraConfig = '' + include "${pkgs.nginx}/conf/fastcgi_params"; + fastcgi_param SCRIPT_FILENAME "${pkgs.zpha.flauschehorn-sexy}/bin/website"; + fastcgi_param DB_PATH "${config.systemd.services.flauschehornFetcher.environment.DB_PATH}"; + fastcgi_param QUERY_STRING $args; + fastcgi_pass unix:${config.services.fcgiwrap.instances.flauschehorn.socket.address}; + ''; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/websites/git.zaphyra.eu.nix b/nixosModules/zpha/websites/git.zaphyra.eu.nix @@ -0,0 +1,273 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.websites."git.zaphyra.eu".enable = lib.mkEnableOption ""; + + config = lib.mkIf config.zpha.websites."git.zaphyra.eu".enable ( + let + adminPubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILTOkSPzd70xNTgPOylix/lb5JKYdt+URoewlmGRP3oZ"; + title = "zaphyra's git"; + mail = "git@zaphyra.eu"; + categories = [ + "nix" + "etc" + "javascript" + "golang" + "nimlang" + "nimlang libraries" + "archive" + ]; + + stagitFunctions = pkgs.writeShellScript "stagitFunctions" '' + is_public_and_listed() { + if [ ! -f "$1/git-daemon-export-ok" ]; then + return 1 + fi + return 0 + } + + is_forced_update() { + test "$oldrev" = "0000000000000000000000000000000000000000" && return 1 + test "$newrev" = "0000000000000000000000000000000000000000" && return 1 + + hasrevs="$(${pkgs.git}/bin/git rev-list "$oldrev" "^$newrev" | ${pkgs.gnused}/bin/sed 1q)" + if test -n "$hasrevs"; then + return 0 + fi + return 1 + } + + build_stagit_repo() { + reponame="$(basename "$1" ".git")" + printf "[%s] Generate stagit HTML pages... " "$reponame" + + mkdir -p "${config.common.services.gitolite.dataDir}/stagit-cache" + mkdir -p "/var/lib/stagit/$reponame" + + cd "/var/lib/stagit/$reponame" || return 1 + + # build repo pages + ${lib.getExe pkgs.zpha.stagit} -c "${config.common.services.gitolite.dataDir}/stagit-cache/$reponame" -n "${title}" -h 'https://git.zaphyra.eu/' -s 'git@git.zaphyra.eu:' "$1" + + # set correct permissions + chown git:git -R /var/lib/stagit/$reponame; + chmod 755 -R /var/lib/stagit/$reponame; + + echo "done" + } + + build_stagit_index() { + printf "Generating stagit index... " + + # set assets if not already there + ln -sf "${pkgs.zpha.stagit}/share/doc/stagit/highlight.min.js" "/var/lib/stagit/highlight.min.js" 2> /dev/null + ln -sf "${pkgs.zpha.stagit}/share/doc/stagit/style.css" "/var/lib/stagit/style.css" 2> /dev/null + + # generate index arguments + args="-n \"${title}\" -e '${mail}'" + + for category in ${lib.escapeShellArgs categories}; do + args="$args -c '$category'" + for repo in "$HOME/repositories/"*.git/; do + repo="''${repo%/}" + is_public_and_listed "$repo" || continue + + [ "$(${pkgs.gawk}/bin/awk -F '=' '/category/ {print $2}' $repo/config | ${pkgs.gnused}/bin/sed -e 's/^[[:space:]]*//')" = "$category" ] && args="$args $repo" + done + done + + # build index + echo "$args" | xargs ${pkgs.zpha.stagit}/bin/stagit-index > /var/lib/stagit/index.html + + # set correct permissions + chown git:git /var/lib/stagit/index.html; + chmod 755 /var/lib/stagit/index.html; + + echo "done" + } + + + update_stagit_repo() { + repo="$(pwd)" + reponame="$(basename "$repo" ".git")" + + cd "$repo" || return 1 + is_public_and_listed "$repo" || return 0 + + # if forced update, remove directory and cache file + is_forced_update && printf "[%s] Forced update, trigger complete regeneration of stagit-pages... \n" "$reponame" && rm -rf "/var/lib/stagit/$reponame" "/var/lib/gitolite/stagit-cache/$reponame" + + build_stagit_repo "$repo" + build_stagit_index + } + + ''; + + rebuildWebdir = '' + source ${stagitFunctions} + + # clear webdir + rm -rf /var/lib/stagit/* + + # clear cache + rm -rf ${config.common.services.gitolite.dataDir}/stagit-cache/* + + # generate pages per repo + for repo in "$HOME/repositories/"*.git/; do + repo="''${repo%/}" + is_public_and_listed "$repo" || continue + + build_stagit_repo "$repo" + done + + # generate index page + build_stagit_index + ''; + + in + { + dns.zones = { + "zaphyra.eu".subdomains."git".CNAME = lib.singleton "${config.networking.fqdn}."; + "katja.wtf".subdomains."git".CNAME = lib.singleton "${config.networking.fqdn}."; + "ctu.cx".subdomains."git".CNAME = lib.singleton "${config.networking.fqdn}."; + }; + + sops.secrets = { + "restic/gitolite/repositoryPassword" = { }; + "restic/gitolite/sshPrivateKey" = { }; + }; + + common = { + configure.persist.system.dirs = [ + { + directory = "/var/lib/stagit"; + mode = "0755"; + inherit (config.common.services.gitolite) user group; + } + ]; + + services.resticBackup.gitolite = { + inherit (config.common.services.gitolite) user; + enable = true; + targets = [ "isodon.fc9f.de" ]; + sshKeyFile = config.sops.secrets."restic/gitolite/sshPrivateKey".path; + passwordFile = config.sops.secrets."restic/gitolite/repositoryPassword".path; + paths = [ config.common.services.gitolite.dataDir ]; + }; + + services.gitolite = { + enable = true; + user = "git"; + group = "git"; + inherit adminPubkey; + + extraGitoliteRc = '' + $RC{GIT_CONFIG_KEYS} = ".*"; + $RC{UMASK} = 0027; + + push(@{$RC{ENABLE}}, 'cgit'); + push(@{$RC{ENABLE}}, 'symbolic-ref'); + push(@{$RC{ENABLE}}, 'rebuild-webdir'); + push(@{$RC{ENABLE}}, 'rebuild-webdir'); + + $RC{NON_CORE} = "rebuild-webdir-trigger POST_COMPILE rebuild-stagit"; + ''; + + triggers.rebuild-webdir = rebuildWebdir; + commands.rebuild-webdir = rebuildWebdir; + commonHooks.post-receive = '' + # update stagit pages + source ${stagitFunctions} + update_stagit_repo "$1" + ''; + }; + }; + + services = { + fcgiwrap = { + instances.git = { + process.user = "git"; + process.group = "git"; + socket.user = "nginx"; + socket.group = "nginx"; + }; + }; + + nginx = { + enable = true; + virtualHosts = { + "git.ctu.cx" = { + serverAliases = [ "git.katja.wtf" ]; + useACMEHost = config.networking.fqdn; + forceSSL = true; + kTLS = true; + locations."/".return = "307 https://git.zaphyra.eu$request_uri"; + }; + + "git.zaphyra.eu" = { + useACMEHost = config.networking.fqdn; + forceSSL = true; + kTLS = true; + root = "/var/lib/stagit"; + locations = { + "@redir".return = "307 ../log.html"; + "~ '^/([a-zA-Z0-9_.]+)/commit/.*$'".extraConfig = "error_page 404 = @redir;"; + + "~* \.html$".extraConfig = '' + add_header Last-Modified $date_gmt; + add_header Cache-Control 'private no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; + if_modified_since off; + expires off; + etag off; + ''; + + "~ '^/[a-zA-Z0-9._-]+/raw'".extraConfig = '' + types { + application/json json; + + application/wasm wasm; + font/woff woff; + font/woff2 woff2; + + application/pdf pdf; + + image/gif gif; + image/jpeg jpeg jpg; + image/png png; + image/svg+xml svg svgz; + image/webp webp; + image/x-icon ico; + } + + default_type text/plain; + try_files $uri =404; + ''; + + "~ '^/[a-zA-Z0-9._-]+/(git-(receive|upload)-pack|HEAD|info/refs|objects/(info/(http-)?alternates|packs)|[0-9a-f]{2}/[0-9a-f]{38}|pack/pack-[0-9a-f]{40}\.(pack|idx))$'".extraConfig = + '' + if ($query_string = service=git-receive-pack) { + return 403; + } + + include "${pkgs.nginx}/conf/fastcgi_params"; + fastcgi_param SCRIPT_FILENAME "${pkgs.git}/libexec/git-core/git-http-backend"; + fastcgi_param GIT_PROJECT_ROOT /var/lib/gitolite/repositories; + fastcgi_param PATH_INFO $uri; + fastcgi_pass unix:${config.services.fcgiwrap.instances.git.socket.address}; + ''; + }; + }; + }; + }; + + }; + } + ); + +}
diff --git a/nixosModules/zpha/websites/gomuks.zaphyra.eu.nix b/nixosModules/zpha/websites/gomuks.zaphyra.eu.nix @@ -0,0 +1,46 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.websites."gomuks.zaphyra.eu".enable = lib.mkEnableOption ""; + + config = lib.mkIf config.zpha.websites."gomuks.zaphyra.eu".enable { + dns.zones."zaphyra.eu".subdomains."gomuks".CNAME = [ "${config.networking.fqdn}." ]; + + common.configure.persist.system.dirs = [ "/var/lib/private/gomuks-zaphyra" ]; + + sops.secrets."gomuks/username" = { }; + sops.secrets."gomuks/password" = { }; + + systemd.services.gomuks-zaphyra.serviceConfig.LoadCredential = [ + "WEB_USERNAME:${config.sops.secrets."gomuks/username".path}" + "WEB_PASSWORD:${config.sops.secrets."gomuks/password".path}" + ]; + + services = { + gomuks.zaphyra = { + package = pkgs.unstable.gomuks-web; + settings = { + web.listen_address = "[::1]:29325"; + web.username = "\${WEB_USERNAME}"; + web.password_hash = "\${WEB_PASSWORD}"; + }; + }; + nginx.virtualHosts."gomuks.zaphyra.eu" = { + useACMEHost = config.networking.fqdn; + forceSSL = true; + kTLS = true; + locations."/" = { + proxyPass = "http://${config.services.gomuks.zaphyra.settings.web.listen_address}"; + proxyWebsockets = true; + }; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/websites/gts.zaphyra.eu.nix b/nixosModules/zpha/websites/gts.zaphyra.eu.nix @@ -0,0 +1,212 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.websites."gts.zaphyra.eu".enable = lib.mkEnableOption ""; + + config = lib.mkIf config.zpha.websites."gts.zaphyra.eu".enable { + dns.zones."zaphyra.eu".subdomains."gts".CNAME = [ "${config.networking.fqdn}." ]; + + users = { + users.gotosocial.uid = 818; + groups.gotosocial.gid = 818; + }; + + sops.secrets = { + "restic/gotosocial/repositoryPassword" = { }; + "restic/gotosocial/sshPrivateKey" = { }; + "environments/gotosocial" = { + restartUnits = [ "gotosocial.service" ]; + }; + }; + + systemd.tmpfiles.settings.gotosocial = { + "/var/lib/gotosocial/storage".d = { + inherit (config.common.services.gotosocial) user group; + mode = "750"; + age = "-"; + }; + }; + + common = { + configure.persist.system.dirs = lib.singleton { + inherit (config.common.services.gotosocial) user group; + directory = config.common.services.gotosocial.stateDir; + mode = "0755"; + }; + + services.resticBackup.gotosocial = { + enable = true; + inherit (config.common.services.gotosocial) user; + targets = [ + "restic-target.fc9f.de" + "isodon.fc9f.de" + ]; + sshKeyFile = config.sops.secrets."restic/gotosocial/sshPrivateKey".path; + passwordFile = config.sops.secrets."restic/gotosocial/repositoryPassword".path; + sqliteDatabases = [ + (lib.mkIf ( + config.common.services.gotosocial.settings.db-type == "sqlite" + ) config.common.services.gotosocial.settings.db-address) + ]; + paths = [ + (lib.mkIf ( + config.common.services.gotosocial.settings.storage-backend == "local" + ) config.common.services.gotosocial.settings.storage-local-base-path) + "${config.common.services.gotosocial.stateDir}/backup.json" + ]; + runBeforeBackup = '' + ${config.common.services.gotosocial.package}/bin/gotosocial --config-path /etc/gotosocial.yaml admin export --path ${config.common.services.gotosocial.stateDir}/backup.json + ''; + }; + }; + + common.services.gotosocial = { + enable = true; + package = pkgs.zpha.gotosocial-unstable; + inherit (config.services.nginx) group; + environmentFile = config.sops.secrets."environments/gotosocial".path; + settings = { + protocol = "https"; + + bind-address = "::1"; + port = 8085; + + trusted-proxies = [ + "::1/128" + "172.17.0.0/24" + ]; + + db-type = "sqlite"; + db-address = "${config.common.services.gotosocial.stateDir}/database.sqlite"; + + storage-backend = "local"; + storage-local-base-path = "${config.common.services.gotosocial.stateDir}/storage"; + + host = "gts.zaphyra.eu"; + account-domain = "zaphyra.eu"; + + landing-page-user = "katja"; + + accounts-allow-custom-css = true; + accounts-registration-open = false; + + instance-expose-peers = true; + instance-expose-suspended = true; + instance-expose-suspended-web = true; + + instance-languages = [ + "en" + "de" + ]; + + media-emoji-local-max-size = "100KiB"; + + media-local-max-size = "50MiB"; + media-remote-max-size = "50MiB"; + + media-remote-cache-duration = "3 days"; + media-cleanup-cron = "30 23 * * *"; + + smtp-host = "morio.infra.zaphyra.eu"; + smtp-port = 587; + smtp-username = "gts@zaphyra.eu"; + smtp-from = "gts@zaphyra.eu"; + + advanced-rate-limit-requests = 3000; + }; + }; + + services.nginx = { + appendHttpConfig = '' + proxy_cache_path /var/cache/nginx keys_zone=gotosocial_ap_public_responses:10m inactive=1w; + ''; + virtualHosts = { + "${config.common.services.gotosocial.settings.host}" = { + useACMEHost = lib.mkDefault config.networking.fqdn; + forceSSL = lib.mkDefault true; + kTLS = lib.mkDefault true; + locations = { + "/" = { + proxyPass = "http://[${toString config.common.services.gotosocial.settings.bind-address}]:${toString config.common.services.gotosocial.settings.port}"; + proxyWebsockets = true; + }; + + "/client/" = { + index = "index.html"; + alias = "${ + pkgs.zpha.phanpy.override { + clientName = "zaphyra's fedi"; + website = "https://gts.zaphyra.eu/client/"; + defaultInstance = "gts.zaphyra.eu"; + } + }/"; + }; + + "~ /.well-known/(webfinger|host-meta)$" = { + proxyPass = "http://[${toString config.common.services.gotosocial.settings.bind-address}]:${toString config.common.services.gotosocial.settings.port}"; + extraConfig = '' + proxy_cache gotosocial_ap_public_responses; + proxy_cache_background_update on; + proxy_cache_key $scheme://$host$uri$is_args$query_string; + proxy_cache_valid 200 10m; + proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504 http_429; + proxy_cache_lock on; + add_header X-Cache-Status $upstream_cache_status; + ''; + }; + + "~ ^\/users\/(?:[a-z0-9_\.]+)\/main-key$" = { + proxyPass = "http://[${toString config.common.services.gotosocial.settings.bind-address}]:${toString config.common.services.gotosocial.settings.port}"; + extraConfig = '' + proxy_cache gotosocial_ap_public_responses; + proxy_cache_background_update on; + proxy_cache_key $scheme://$host$uri; + proxy_cache_valid 200 604800s; + proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504 http_429; + proxy_cache_lock on; + + add_header X-Cache-Status $upstream_cache_status; + ''; + }; + + "/assets/".extraConfig = '' + alias ${config.common.services.gotosocial.package}/share/gotosocial/web/assets/; + autoindex off; + expires max; + add_header Cache-Control "public, immutable"; + ''; + }; + }; + } + // ( + if + ( + config.common.services.gotosocial.settings.account-domain + != config.common.services.gotosocial.settings.host + ) + then + { + "${config.common.services.gotosocial.settings.account-domain}" = { + locations = { + "= /.well-known/host-meta".extraConfig = + "return 301 https://${config.common.services.gotosocial.settings.host}$request_uri;"; + "= /.well-known/webfinger".extraConfig = + "return 301 https://${config.common.services.gotosocial.settings.host}$request_uri;"; + "= /.well-known/nodeinfo".extraConfig = + "return 301 https://${config.common.services.gotosocial.settings.host}$request_uri;"; + }; + }; + } + else + { } + ); + }; + }; + +}
diff --git a/nixosModules/zpha/websites/hass.zaphyra.eu.nix b/nixosModules/zpha/websites/hass.zaphyra.eu.nix @@ -0,0 +1,82 @@ +{ + machineConfig, + sopsSecrets, + resources, + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.websites."hass.zaphyra.eu".enable = lib.mkEnableOption ""; + + config = lib.mkIf config.zpha.websites."hass.zaphyra.eu".enable { + dns.zones = { + "zaphyra.eu".subdomains."hass".AAAA = lib.singleton machineConfig.networking.dn42.ip6Address; + "zaphyra.dn42".subdomains."hass".AAAA = lib.singleton machineConfig.networking.dn42.ip6Address; + "fc9f.de".subdomains."floractl".A = lib.singleton machineConfig.networking.ip4Address; + }; + + sops.secrets."floractl/config.json" = { + sopsFile = sopsSecrets.zaphyra.floractl; + key = "config"; + owner = "nginx"; + group = "nginx"; + }; + + security.acme.certs."hass.zaphyra.dn42" = { + server = "https://acme.burble.dn42/v1/dn42/acme/directory"; + validMinDays = 20; + keyType = "ec384"; + dnsProvider = null; + }; + + services.nginx.virtualHosts = + let + vHost = { + forceSSL = true; + kTLS = true; + locations."/".extraConfig = '' + allow fd6b:6174:6a61::/48; + allow fd42:ccc:da::/48; + deny all; + + proxy_pass http://192.168.2.147:8123; + proxy_http_version 1.1; + proxy_set_header Host 192.168.2.147; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + ''; + }; + + in + { + "hass.zaphyra.dn42" = vHost // { + enableACME = true; + }; + "hass.zaphyra.eu" = vHost // { + useACMEHost = config.networking.fqdn; + }; + "floractl.fc9f.de" = { + forceSSL = true; + useACMEHost = config.networking.fqdn; + kTLS = true; + root = pkgs.zpha.mqtt-webui.override { + patches = [ + resources.patches.mqttwebui-florapatches-owo + ]; + }; + locations = { + "= /config.json".alias = config.sops.secrets."floractl/config.json".path; + "/mqtt" = { + proxyPass = "http://192.168.2.147:1884/"; + proxyWebsockets = true; + }; + }; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/websites/ip.fc9f.de.nix b/nixosModules/zpha/websites/ip.fc9f.de.nix @@ -0,0 +1,95 @@ +{ + machineConfig, + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.websites."ip.fc9f.de".enable = lib.mkEnableOption ""; + + config = lib.mkIf config.zpha.websites."ip.fc9f.de".enable { + dns.zones."fc9f.de".subdomains = { + "ip" = + pkgs.dnsNix.combinators.host machineConfig.networking.ip4Address machineConfig.networking.ip6Address; + "ip4".A = [ machineConfig.networking.ip4Address ]; + "ip6".AAAA = [ machineConfig.networking.ip6Address ]; + }; + + services.nginx.virtualHosts = { + "ip.fc9f.de" = { + useACMEHost = config.networking.fqdn; + forceSSL = true; + kTLS = true; + locations."/" = { + extraConfig = "types { } default_type 'text/html; charset=utf-8';"; + return = '' + 200 ' + <!DOCTYPE html> + <html> + <head> + <title>ip.fc9f.de</title> + </head> + <body> + <h1>ip.fc9f.de</h1> + <ul> + <li><span style="user-select: none;"><b>IPv6:</b> </span><span id="ip6">Loading...</span></li> + <li><span style="user-select: none;"><b>IPv4:</b> </span><span id="ip4">Loading...</span></li> + </ul> + <p>Use bash and curl: <code>curl ip{4,6}.zaphyra.eu</code></p> + <p><small>Because any other "Whats my IP?"-tool sucks. <a href="https://git.clerie.de/clerie/ip.clerie.de">Host yourself :3</a></small></p> + + <script> + window.addEventListener("DOMContentLoaded", (event) => { + [ "ip6", "ip4" ].forEach(async (ipVersion) => { + try { + const url = "https://" + ipVersion + ".fc9f.de/"; + const response = await fetch(url + ((/\?/).test(url) ? "&" : "?") + (new Date()).getTime()); + if(response.status != 200) { + document.getElementById(ipVersion).innerText = "Error!"; + } else { + document.getElementById(ipVersion).innerText = await response.text(); + } + } catch { + document.getElementById(ipVersion).innerText = "Error!"; + } + }); + }); + </script> + </body> + </html>' + ''; + }; + }; + + "ip4.fc9f.de" = { + useACMEHost = config.networking.fqdn; + forceSSL = true; + kTLS = true; + locations."/" = { + return = "200 '$remote_addr\n'"; + extraConfig = '' + types { } default_type "text/plain; charset=utf-8"; + add_header Access-Control-Allow-Origin *; + ''; + }; + }; + + "ip6.fc9f.de" = { + useACMEHost = config.networking.fqdn; + forceSSL = true; + kTLS = true; + locations."/" = { + return = "200 '$remote_addr\n'"; + extraConfig = '' + types { } default_type "text/plain; charset=utf-8"; + add_header Access-Control-Allow-Origin *; + ''; + }; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/websites/links.zaphyra.eu.nix b/nixosModules/zpha/websites/links.zaphyra.eu.nix @@ -0,0 +1,55 @@ +{ + config, + lib, + ... +}: + +{ + + options.zpha.websites."links.zaphyra.eu".enable = lib.mkEnableOption ""; + + config = lib.mkIf config.zpha.websites."links.zaphyra.eu".enable { + dns.zones."zaphyra.eu".subdomains."links".CNAME = [ "${config.networking.fqdn}." ]; + + # sops.secrets = { + # "restic/memos/repositoryPassword" = { }; + # "restic/memos/sshPrivateKey" = { }; + # }; + + common = { + configure.persist.system.dirs = [ "/var/lib/private/shiori" ]; + + # services.resticBackup.memos = { + # inherit (config.services.memos) user; + # enable = true; + # targets = [ + # "restic-target.fc9f.de" + # "isodon.fc9f.de" + # ]; + # sshKeyFile = config.sops.secrets."restic/memos/sshPrivateKey".path; + # passwordFile = config.sops.secrets."restic/memos/repositoryPassword".path; + # sqliteDatabases = [ "${config.services.memos.dataDir}/memos_prod.db" ]; + # paths = [ "${config.services.memos.dataDir}/uploads" ]; + # }; + }; + + services = { + postgresql.enable = true; + shiori = { + enable = true; + address = "[::1]"; + port = 7523; + }; + nginx.virtualHosts."links.zaphyra.eu" = { + useACMEHost = config.networking.fqdn; + forceSSL = true; + kTLS = true; + locations."/" = { + proxyPass = "http://${toString config.services.shiori.address}:${toString config.services.shiori.port}"; + proxyWebsockets = true; + }; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/websites/memories.zaphyra.eu.nix b/nixosModules/zpha/websites/memories.zaphyra.eu.nix @@ -0,0 +1,82 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.websites."memories.zaphyra.eu".enable = lib.mkEnableOption ""; + + config = lib.mkIf config.zpha.websites."memories.zaphyra.eu".enable { + dns.zones."zaphyra.eu".subdomains."memories".CNAME = [ "${config.networking.fqdn}." ]; + + sops.secrets = { + "restic/immich/repositoryPassword" = { }; + "restic/immich/sshPrivateKey" = { }; + }; + + common = { + configure.persist.system.dirs = [ + "/var/lib/postgresql" + { + inherit (config.services.immich) user group; + directory = config.services.immich.mediaLocation; + mode = "0750"; + } + ]; + + services.resticBackup.immich = { + inherit (config.services.immich) user; + enable = true; + targets = [ + "restic-target.fc9f.de" + "isodon.fc9f.de" + ]; + sshKeyFile = config.sops.secrets."restic/immich/sshPrivateKey".path; + passwordFile = config.sops.secrets."restic/immich/repositoryPassword".path; + postgresDatabases = [ config.services.immich.database.name ]; + paths = [ config.services.immich.mediaLocation ]; + }; + }; + + services = { + immich = { + enable = true; + package = pkgs.unstable.immich; + host = "::1"; + redis.enable = true; + machine-learning.enable = true; + settings = { + server.externalDomain = "https://memories.zaphyra.eu"; + backup.database.enabled = false; + metadata.faces."import" = true; + storageTemplate = { + enabled = false; + hashVerificationEnabled = true; + template = "{{y}}/{{y}}{{MM}}/{{dd}}-{{filename}}"; + }; + }; + }; + + nginx.virtualHosts."memories.zaphyra.eu" = { + useACMEHost = "${config.networking.fqdn}"; + forceSSL = true; + kTLS = true; + locations."/" = { + proxyPass = "http://[${config.services.immich.host}]:${toString config.services.immich.port}"; + proxyWebsockets = true; + recommendedProxySettings = true; + extraConfig = '' + client_max_body_size 50000M; + proxy_read_timeout 600s; + proxy_send_timeout 600s; + send_timeout 600s; + ''; + }; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/websites/music.zaphyra.eu.nix b/nixosModules/zpha/websites/music.zaphyra.eu.nix @@ -0,0 +1,204 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.zpha.websites."music.zaphyra.eu"; + +in +{ + + options.zpha.websites."music.zaphyra.eu" = { + enable = lib.mkEnableOption ""; + subdomain = lib.mkOption { + type = lib.types.str; + default = "music"; + }; + }; + + config = lib.mkIf cfg.enable { + dns.zones."zaphyra.eu".subdomains."${cfg.subdomain}".CNAME = [ "${config.networking.fqdn}." ]; + + sops.secrets."environments/navidrome" = { + restartUnits = [ "navidrome.service" ]; + }; + + users = { + users = { + navidrome.uid = config.services.navidrome.settings.Port; + piegames = { + isNormalUser = true; + createHome = true; + home = "/mnt/music/piegames"; + group = "navidrome"; + extraGroups = [ "ssh" ]; + openssh.authorizedKeys.keys = config.users.users.zaphyra.openssh.authorizedKeys.keys ++ [ + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC0+vFmtDUgIg18r6a2ezNBRMrZVpAB6dhj37JDhYUu7CSr7o1pR8Sp/UUe/yAEjeyo0R/GfvKIKugCBDCd+6VsDuTkcWX9bwVdrelG3X7pIIt3tWdbqVVGZHFJ/qgdngvIYu3pY++ci6zFlBl8Z8SIXYXpDnVFYTvvBhDIOZJzvyyqL7B/Wm19fTf/HPLTtbuPFMg2gQxo8o5GW3Ow2keJRR1daSGYmGSA1y/F3UumoLuK85Zm0+Kt+yYTO2pMAUBN7Axv1yvnNGbdX9l+3eRHdKatfhCscs720cwR+HtMAfqvN1G5FAWnXdxPa8XIzyZIyrWRoEGPPuK1w2GlWUnDdYLVDMJOmLDz4TvYx2eodfWMRIdppLJYwWpv+Eg8JirO5bnbOnKzVM4C5ESYU8b1mRBI23ncqV+BAno+zehdzLPYg0q3N9wNDF6jqyw08YyiTDwlua4aZJzHDrDt7OjcFaC6t8W1/VimtE0bylhAzVeenJ8C+o3q2jPBvuNLIxk= piegames" + ]; + }; + music = { + isNormalUser = true; + home = "/mnt/music/inbox"; + group = "users"; + extraGroups = [ "ssh" ]; + openssh.authorizedKeys.keys = config.users.users.zaphyra.openssh.authorizedKeys.keys ++ [ + # yuka + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGbzUmOJuuAYn/3ODyw3WKjz7SnKjMq4iHE+mEpwVVmw" + # rootile + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINkk3bjlkJWq5y4S9sMfW2Wzo5jQFodVqm/Vn226etjW" + # ember + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFsEf6O4VWZFtIholRze7bUi/emzpwTQL4wRJaoPSzrG" + "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJWEf+o3fw937guIiiZs8hYiBwrhkrxhul3xr0eRUI2RVGz9OHVOdz6WC5W0+lvQZAIeCmEyCF6DwX7aUANXqUg= misc@secretive.Ember’s-MacBook-Pro.local" + # nyaaa (nia) + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMyMojaOLRaEb8+aMrdUZVfTN2V/VoUW8NeyQyNGOedu fly@kinkpad" + ]; + }; + }; + groups.navidrome.gid = config.services.navidrome.settings.Port; + }; + + sops.secrets = { + "restic/navidrome/repositoryPassword" = { }; + "restic/navidrome/sshPrivateKey" = { }; + }; + + fileSystems."/mnt/music-ro" = { + device = config.services.syncthing.settings.folders.zaphyra-music-orig.path; + fsType = "fuse.bindfs"; + options = [ + "ro" + "perms=0000:a+rX" + ]; + }; + + common = { + configure.persist.system.dirs = [ + { + inherit (config.services.navidrome) user group; + directory = "/var/lib/navidrome"; + mode = "0700"; + } + { + inherit (config.services.navidrome) user group; + directory = "/var/cache/navidrome"; + mode = "0700"; + } + ]; + + services.openssh.enableRSASupport = true; + services.resticBackup.navidrome = { + inherit (config.services.navidrome) user; + enable = true; + targets = [ + "restic-target.fc9f.de" + "isodon.fc9f.de" + ]; + sshKeyFile = config.sops.secrets."restic/navidrome/sshPrivateKey".path; + passwordFile = config.sops.secrets."restic/navidrome/repositoryPassword".path; + postgresDatabases = [ "audiomuse" ]; + sqliteDatabases = [ "${config.services.navidrome.settings.DataFolder}/navidrome.db" ]; + runBeforeBackup = '' + ${config.systemd.services.navidrome.serviceConfig.ExecStart} backup create + ${config.systemd.services.navidrome.serviceConfig.ExecStart} backup prune + ''; + paths = [ + config.services.navidrome.settings.Backup.Path + ]; + }; + }; + + systemd.services.navidrome.serviceConfig = { + MemoryDenyWriteExecute = lib.mkForce false; + BindReadOnlyPaths = [ "/mnt/music-ro" ]; + }; + + networking.firewall.allowedTCPPorts = [ config.services.postgresql.settings.port ]; + + services = { + openssh.extraConfig = '' + Match User "music" + # AllowTcpForwarding no + X11Forwarding no + ''; + + postgresql = { + enable = true; + settings = { + ssl = true; + password_encryption = "scram-sha-256"; + }; + enableTCPIP = true; + authentication = lib.mkOverride 10 '' + #type database DBuser origin-address auth-method + local all all peer + # ipv4 + host audiomuse audiomuse 172.0.0.0/8 scram-sha-256 + hostssl audiomuse audiomuse ::/0 scram-sha-256 + ''; + ensureDatabases = [ "audiomuse" ]; + ensureUsers = [ + { + name = "audiomuse"; + ensureDBOwnership = true; + ensureClauses.login = true; + } + ]; + }; + + navidrome = { + enable = true; + environmentFile = config.sops.secrets."environments/navidrome".path; + package = pkgs.unstable.navidrome.overrideAttrs { + CGO_CFLAGS_ALLOW = ".*--define-prefix.*"; + }; + settings = { + Address = "0.0.0.0"; + BaseUrl = "https://${cfg.subdomain}.zaphyra.eu"; + MusicFolder = "/mnt/music-ro/defaultLibrary"; + Deezer.Enabled = true; + LastFM.Enabled = true; + EnableInsightsCollector = false; + EnableExternalServices = true; + EnableSharing = true; + EnableArtworkPrecache = true; + FFmpegPath = lib.getExe pkgs.ffmpeg; + DataFolder = "/var/lib/navidrome/state"; + CacheFolder = "/var/cache/navidrome"; + ImageCacheSize = "2GB"; + TranscodingCacheSize = "10GB"; + UIWelcomeMessage = "sailing on pcm waves"; + Agents = "audiomuseai,lastfm,spotify,deezer"; + Backup = { + Path = "/var/lib/navidrome/backup"; + Count = 2; + Schedule = "0 0 * * *"; + }; + Plugins = { + Enabled = true; + Folder = "/var/lib/navidrome/plugins"; # Optional: custom plugins folder + AutoReload = true; # Useful during development/testing + LogLevel = "debug"; # Enable detailed plugin logging + CacheSize = "200MB"; + }; + }; + }; + + nginx.virtualHosts."${cfg.subdomain}.zaphyra.eu" = { + useACMEHost = config.networking.fqdn; + forceSSL = true; + kTLS = true; + root = pkgs.zpha.domsonic; + locations = { + "/".tryFiles = "$uri $uri/ /index.html"; + } + // (lib.genAttrs [ "/rest" "/auth" "/api" "/app" "/share" ] (name: { + proxyPass = "http://127.0.0.1:${toString config.services.navidrome.settings.Port}"; + })); + }; + }; + }; + +}
diff --git a/nixosModules/zpha/websites/notes.zaphyra.eu.nix b/nixosModules/zpha/websites/notes.zaphyra.eu.nix @@ -0,0 +1,66 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.websites."notes.zaphyra.eu".enable = lib.mkEnableOption ""; + + config = lib.mkIf config.zpha.websites."notes.zaphyra.eu".enable { + dns.zones."zaphyra.eu".subdomains."notes".CNAME = [ "${config.networking.fqdn}." ]; + + sops.secrets = { + "restic/memos/repositoryPassword" = { }; + "restic/memos/sshPrivateKey" = { }; + }; + + common = { + configure.persist.system.dirs = lib.singleton { + directory = "/var/lib/memos"; + mode = "700"; + inherit (config.services.memos) user group; + }; + + services.resticBackup.memos = { + inherit (config.services.memos) user; + enable = true; + targets = [ + "restic-target.fc9f.de" + "isodon.fc9f.de" + ]; + sshKeyFile = config.sops.secrets."restic/memos/sshPrivateKey".path; + passwordFile = config.sops.secrets."restic/memos/repositoryPassword".path; + sqliteDatabases = [ "${config.services.memos.dataDir}/memos_prod.db" ]; + paths = [ "${config.services.memos.dataDir}/uploads" ]; + }; + }; + + services = { + memos = { + enable = true; + package = pkgs.zpha.memos; + settings = { + MEMOS_MODE = "prod"; + MEMOS_DATA = config.services.memos.dataDir; + MEMOS_DRIVER = "sqlite"; + MEMOS_ADDR = "[::1]"; + MEMOS_PORT = "5230"; + MEMOS_INSTANCE_URL = "https://notes.zaphyra.eu"; + }; + }; + nginx.virtualHosts."notes.zaphyra.eu" = { + useACMEHost = config.networking.fqdn; + forceSSL = true; + kTLS = true; + locations."/" = { + proxyPass = "http://${toString config.services.memos.settings.MEMOS_ADDR}:${toString config.services.memos.settings.MEMOS_PORT}"; + proxyWebsockets = true; + }; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/websites/oeffi.zaphyra.eu.nix b/nixosModules/zpha/websites/oeffi.zaphyra.eu.nix @@ -0,0 +1,107 @@ +{ + config, + pkgs, + lib, + ... +}: + +{ + + options.zpha.websites."oeffi.zaphyra.eu".enable = lib.mkEnableOption ""; + + config = lib.mkIf config.zpha.websites."oeffi.zaphyra.eu".enable { + dns.zones = { + "zaphyra.eu".subdomains."oeffi".CNAME = [ "${config.networking.fqdn}." ]; + "katja.wtf".subdomains."oeffi".CNAME = [ "${config.networking.fqdn}." ]; + }; + + services.nginx = { + appendHttpConfig = '' + resolver 1.1.1.1 valid=300s; + + map $vendotarget $vendopath { + default no; + locations '/mob/location/search'; + location '/mob/location/details'; + journeys '/mob/angebote/fahrplan'; + journey '/mob/angebote/recon'; + departures '/mob/bahnhofstafel/abfahrt'; + trip '/mob/zuglauf'; + } + + map $hafastarget $hafasurl { + default no; + nahsh nah.sh.hafas.de; + rmv www.rmv.de; + bvg bvg-apps-ext.hafas.de; + oebb fahrplan.oebb.at; + } + + map $hafastarget $hafaspath { + default no; + nahsh '/bin/mgate.exe'; + rmv '/auskunft/bin/jp/mgate.exe'; + bvg '/bin/mgate.exe'; + oebb '/bin/mgate.exe'; + } + ''; + + virtualHosts."oeffi.zaphyra.eu" = { + serverAliases = [ "oeffi.katja.wtf" ]; + useACMEHost = config.networking.fqdn; + forceSSL = true; + kTLS = true; + root = pkgs.zpha.oeffisearch; + extraConfig = '' + merge_slashes off; + large_client_header_buffers 4 16k; + ''; + + locations = { + "/db/vehicle-sequence".extraConfig = '' + proxy_ssl_server_name on; + proxy_ssl_name www.bahn.de; + proxy_set_header Host www.bahn.de; + proxy_hide_header 'set-cookie'; + proxy_pass https://www.bahn.de/web/api/reisebegleitung/wagenreihung/vehicle-sequence$is_args$args; + ''; + + "~ ^/db/vendo/(?<vendotarget>[a-z]+)(/([^\\r\\n].*))?$".extraConfig = '' + if ($vendopath = no) { + return 400; + } + + if ($vendotarget = 'trip') { + set $vendopath '$vendopath$2'; + } + + if ($vendotarget = 'location') { + set $vendopath '$vendopath$2'; + } + + set $vendodomain 'app.services-bahn.de'; + + proxy_ssl_server_name on; + proxy_ssl_name $vendodomain; + proxy_set_header Host $vendodomain; + proxy_hide_header 'set-cookie'; + proxy_pass https://$vendodomain$vendopath; + ''; + + "~ ^/hafas/(?<hafastarget>.*)$".extraConfig = '' + if ($hafasurl = no) { + return 400; + } + + proxy_ssl_server_name on; + proxy_ssl_name $hafasurl; + proxy_set_header Host $hafasurl; + proxy_hide_header 'set-cookie'; + proxy_pass https://$hafasurl$hafaspath; + ''; + }; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/websites/things.zaphyra.eu.nix b/nixosModules/zpha/websites/things.zaphyra.eu.nix @@ -0,0 +1,73 @@ +{ + npins, + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.websites."things.zaphyra.eu".enable = lib.mkEnableOption ""; + + imports = + (lib.mkIf config.zpha.websites."things.zaphyra.eu".enable [ + "${npins.things}/nixosModule.nix" + ]).content; + + config = lib.mkIf config.zpha.websites."things.zaphyra.eu".enable { + dns.zones."zaphyra.eu".subdomains."things".CNAME = [ + "${config.networking.fqdn}." + ]; + + users = { + users.things.uid = 512; + groups.things.gid = 512; + }; + + sops.secrets = { + "restic/things/repositoryPassword" = { }; + "restic/things/sshPrivateKey" = { }; + }; + + common = { + configure.persist.system.dirs = [ + { + directory = config.services.things.storagePath; + mode = "0700"; + user = "things"; + group = "things"; + } + ]; + + services.resticBackup.things = { + inherit (config.services.things) user; + enable = true; + targets = [ + "restic-target.fc9f.de" + "isodon.fc9f.de" + ]; + sshKeyFile = config.sops.secrets."restic/things/sshPrivateKey".path; + passwordFile = config.sops.secrets."restic/things/repositoryPassword".path; + paths = [ config.services.things.storagePath ]; + }; + }; + + services = { + things = { + enable = true; + package = pkgs.zpha.things; + storagePath = "/var/lib/things"; + nginx.enable = true; + nginx.domain = "things.zaphyra.eu"; + }; + + nginx.virtualHosts."things.zaphyra.eu" = { + useACMEHost = "${config.networking.fqdn}"; + forceSSL = true; + kTLS = true; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/websites/vault.zaphyra.eu.nix b/nixosModules/zpha/websites/vault.zaphyra.eu.nix @@ -0,0 +1,109 @@ +{ + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.websites."vault.zaphyra.eu".enable = lib.mkEnableOption ""; + + config = lib.mkIf config.zpha.websites."vault.zaphyra.eu".enable { + dns.zones."zaphyra.eu".subdomains."vault".CNAME = [ + "${config.networking.fqdn}." + ]; + + users = { + users.vaultwarden.uid = 523; + groups.vaultwarden.gid = 523; + }; + + sops.secrets = { + "restic/vaultwarden/repositoryPassword" = { }; + "restic/vaultwarden/sshPrivateKey" = { }; + "environments/vaultwarden" = { + owner = config.systemd.services.vaultwarden.serviceConfig.User; + group = config.systemd.services.vaultwarden.serviceConfig.Group; + restartUnits = [ "vaultwarden.service" ]; + }; + }; + + systemd = { + services.vaultwarden.after = [ "sops-install-secrets.service" ]; + tmpfiles.settings.vaultwarden = { + "${config.services.vaultwarden.backupDir}".d = { + user = config.systemd.services.vaultwarden.serviceConfig.User; + group = config.systemd.services.vaultwarden.serviceConfig.Group; + mode = "750"; + age = "-"; + }; + }; + }; + + common = { + configure.persist.system.dirs = [ + { + directory = "/var/lib/vaultwarden"; + mode = "0700"; + user = config.systemd.services.vaultwarden.serviceConfig.User; + group = config.systemd.services.vaultwarden.serviceConfig.Group; + } + { + directory = config.services.vaultwarden.backupDir; + mode = "0700"; + user = config.systemd.services.vaultwarden.serviceConfig.User; + group = config.systemd.services.vaultwarden.serviceConfig.Group; + } + ]; + services.resticBackup.vaultwarden = { + enable = true; + targets = [ + "restic-target.fc9f.de" + "isodon.fc9f.de" + ]; + sshKeyFile = config.sops.secrets."restic/vaultwarden/sshPrivateKey".path; + passwordFile = config.sops.secrets."restic/vaultwarden/repositoryPassword".path; + paths = [ config.services.vaultwarden.backupDir ]; + runBeforeBackup = '' + ${lib.getExe' pkgs.systemd "systemctl"} start --wait backup-vaultwarden.service + ''; + }; + }; + + services = { + vaultwarden = { + enable = true; + domain = "vault.zaphyra.eu"; + dbBackend = "sqlite"; + backupDir = "/var/backups/vaultwarden"; + environmentFile = config.sops.secrets."environments/vaultwarden".path; + config = { + ROCKET_ADDRESS = "::1"; + ROCKET_PORT = 8582; + + DOMAIN = "https://vault.zaphyra.eu"; + SIGNUPS_ALLOWED = false; + + PUSH_ENABLED = true; + + SMTP_HOST = "morio.infra.zaphyra.eu"; + SMTP_FROM = "vaultwarden@zaphyra.eu"; + SMTP_USERNAME = "vaultwarden@zaphyra.eu"; + SMTP_PORT = 465; + SMTP_SECURITY = "force_tls"; + }; + }; + nginx.virtualHosts."vault.zaphyra.eu" = { + useACMEHost = "${config.networking.fqdn}"; + forceSSL = true; + kTLS = true; + locations."/" = { + proxyPass = "http://[${config.services.vaultwarden.config.ROCKET_ADDRESS}]:${toString config.services.vaultwarden.config.ROCKET_PORT}/"; + proxyWebsockets = true; + }; + }; + }; + }; + +}
diff --git a/nixosModules/zpha/websites/zaphyra.eu.nix b/nixosModules/zpha/websites/zaphyra.eu.nix @@ -0,0 +1,30 @@ +{ + machineConfig, + config, + lib, + pkgs, + ... +}: + +{ + + options.zpha.websites."zaphyra.eu".enable = lib.mkEnableOption ""; + + config = lib.mkIf config.zpha.websites."zaphyra.eu".enable { + dns.zones."zaphyra.eu" = + pkgs.dnsNix.combinators.host machineConfig.networking.ip4Address machineConfig.networking.ip6Address; + + services.nginx.virtualHosts."zaphyra.eu" = { + useACMEHost = "${config.networking.fqdn}"; + forceSSL = true; + kTLS = true; + root = pkgs.zpha.website; + extraConfig = '' + location /.well-known/openpgpkey { + add_header Access-Control-Allow-Origin * always; + } + ''; + }; + }; + +}
diff --git a/npins/default.nix b/npins/default.nix @@ -0,0 +1,249 @@ +/* + This file is provided under the MIT licence: + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +# Generated by npins. Do not modify; will be overwritten regularly +let + # Backwards-compatibly make something that previously didn't take any arguments take some + # The function must return an attrset, and will unfortunately be eagerly evaluated + # Same thing, but it catches eval errors on the default argument so that one may still call it with other arguments + mkFunctor = + fn: + let + e = builtins.tryEval (fn { }); + in + (if e.success then e.value else { error = fn { }; }) // { __functor = _self: fn; }; + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 + range = + first: last: if first > last then [ ] else builtins.genList (n: first + n) (last - first + 1); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 + stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 + stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); + concatStrings = builtins.concatStringsSep ""; + + # If the environment variable NPINS_OVERRIDE_${name} is set, then use + # the path directly as opposed to the fetched source. + # (Taken from Niv for compatibility) + mayOverride = + name: path: + let + envVarName = "NPINS_OVERRIDE_${saneName}"; + saneName = stringAsChars (c: if (builtins.match "[a-zA-Z0-9]" c) == null then "_" else c) name; + ersatz = builtins.getEnv envVarName; + in + if ersatz == "" then + path + else + # this turns the string into an actual Nix path (for both absolute and + # relative paths) + builtins.trace "Overriding path of \"${name}\" with \"${ersatz}\" due to set \"${envVarName}\"" ( + if builtins.substring 0 1 ersatz == "/" then + /. + ersatz + else + /. + builtins.getEnv "PWD" + "/${ersatz}" + ); + + mkSource = + name: spec: + { + pkgs ? null, + }: + assert spec ? type; + let + # Unify across builtin and pkgs fetchers. + # `fetchGit` requires a wrapper because of slight API differences. + fetchers = + if pkgs == null then + { + inherit (builtins) fetchTarball fetchurl; + # For some fucking reason, fetchGit has a different signature than the other builtin fetchers … + fetchGit = args: (builtins.fetchGit args).outPath; + } + else + { + fetchTarball = + { + url, + sha256, + }: + pkgs.fetchzip { + inherit url sha256; + extension = "tar"; + }; + inherit (pkgs) fetchurl; + fetchGit = + { + url, + submodules, + rev, + name, + narHash, + }: + pkgs.fetchgit { + inherit url rev name; + fetchSubmodules = submodules; + hash = narHash; + }; + }; + + # Dispatch to the correct code path based on the type + path = + if spec.type == "Git" then + mkGitSource fetchers spec + else if spec.type == "GitRelease" then + mkGitSource fetchers spec + else if spec.type == "PyPi" then + mkPyPiSource fetchers spec + else if spec.type == "Channel" then + mkChannelSource fetchers spec + else if spec.type == "Tarball" then + mkTarballSource fetchers spec + else if spec.type == "Container" then + mkContainerSource pkgs spec + else + builtins.throw "Unknown source type ${spec.type}"; + in + spec // { outPath = mayOverride name path; }; + + mkGitSource = + { + fetchTarball, + fetchGit, + ... + }: + { + repository, + revision, + url ? null, + submodules, + hash, + ... + }: + assert repository ? type; + # At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository + # In the latter case, there we will always be an url to the tarball + if url != null && !submodules then + fetchTarball { + inherit url; + sha256 = hash; + } + else + let + url = + if repository.type == "Git" then + repository.url + else if repository.type == "GitHub" then + "https://github.com/${repository.owner}/${repository.repo}.git" + else if repository.type == "GitLab" then + "${repository.server}/${repository.repo_path}.git" + else if repository.type == "Forgejo" then + "${repository.server}/${repository.owner}/${repository.repo}.git" + else + throw "Unrecognized repository type ${repository.type}"; + urlToName = + url: rev: + let + matched = builtins.match "^.*/([^/]*)(\\.git)?$" url; + + short = builtins.substring 0 7 rev; + + appendShort = if (builtins.match "[a-f0-9]*" rev) != null then "-${short}" else ""; + in + "${if matched == null then "source" else builtins.head matched}${appendShort}"; + name = urlToName url revision; + in + fetchGit { + rev = revision; + narHash = hash; + + inherit name submodules url; + }; + + mkPyPiSource = + { fetchurl, ... }: + { + url, + hash, + ... + }: + fetchurl { + inherit url; + sha256 = hash; + }; + + mkChannelSource = + { fetchTarball, ... }: + { + url, + hash, + ... + }: + fetchTarball { + inherit url; + sha256 = hash; + }; + + mkTarballSource = + { fetchTarball, ... }: + { + url, + locked_url ? url, + hash, + ... + }: + fetchTarball { + url = locked_url; + sha256 = hash; + }; + + mkContainerSource = + pkgs: + { + image_name, + image_tag, + image_digest, + ... + }: + if pkgs == null then + builtins.throw "container sources require passing in a Nixpkgs value: https://github.com/andir/npins/blob/master/README.md#using-the-nixpkgs-fetchers" + else + pkgs.dockerTools.pullImage { + imageName = image_name; + imageDigest = image_digest; + finalImageTag = image_tag; + }; +in +mkFunctor ( + { + input ? ./sources.json, + }: + let + data = + if builtins.isPath input then + # while `readFile` will throw an error anyways if the path doesn't exist, + # we still need to check beforehand because *our* error can be caught but not the one from the builtin + # *piegames sighs* + if builtins.pathExists input then + builtins.fromJSON (builtins.readFile input) + else + throw "Input path ${toString input} does not exist" + else if builtins.isAttrs input then + input + else + throw "Unsupported input type ${builtins.typeOf input}, must be a path or an attrset"; + version = data.version; + in + if version == 7 then + builtins.mapAttrs (name: spec: mkFunctor (mkSource name spec)) data.pins + else + throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`" +)
diff --git a/npins/sources.json b/npins/sources.json @@ -0,0 +1,297 @@ +{ + "pins": { + "continuwuity": { + "type": "GitRelease", + "repository": { + "type": "Forgejo", + "server": "https://forgejo.ellis.link/", + "owner": "continuwuation", + "repo": "continuwuity" + }, + "pre_releases": false, + "version_upper_bound": null, + "release_prefix": null, + "submodules": false, + "version": "v0.5.9", + "revision": "f668b4a64be61373d2f7d2be96917457558b962c", + "url": "https://forgejo.ellis.link/api/v1/repos/continuwuation/continuwuity/archive/v0.5.9.tar.gz", + "hash": "sha256-4zs26kTqwkJV7x+Sm12LnR02bbyH0f6Itbz7bDKUyts=" + }, + "dnsNix": { + "type": "Git", + "repository": { + "type": "Git", + "url": "https://git.zaphyra.eu/dns.nix" + }, + "branch": "master", + "submodules": false, + "revision": "190f11f966723e89922c2c7cef7e060211814afd", + "url": null, + "hash": "sha256-GgFgW4wbSVhzc+ZSb0Nu7aJGs3iy0wGgJmIDbegcrQE=" + }, + "firefoxGnomeTheme": { + "type": "GitRelease", + "repository": { + "type": "GitHub", + "owner": "rafaelmardojai", + "repo": "firefox-gnome-theme" + }, + "pre_releases": false, + "version_upper_bound": null, + "release_prefix": null, + "submodules": false, + "version": "v149.1", + "revision": "59464fcbe0ffbb15ef2114d4941c71b51005a585", + "url": "https://api.github.com/repos/rafaelmardojai/firefox-gnome-theme/tarball/refs/tags/v149.1", + "hash": "sha256-QFY6Eu0kmaWl8W76bXs5K2BVtTh+Md+1rGba1WiTYxU=" + }, + "flauschehornSexy": { + "type": "Git", + "repository": { + "type": "Git", + "url": "https://git.zaphyra.eu/flauschehorn.sexy" + }, + "branch": "master", + "submodules": false, + "revision": "93dfb56c9fdc45b4947b3717efdb621f7feeceaf", + "url": null, + "hash": "sha256-A1faTJ/x7SDIAPLGG2Qy/WTVqVdfvM7fB2pEnoplqTs=" + }, + "gpxMap": { + "type": "Git", + "repository": { + "type": "Git", + "url": "https://git.zaphyra.eu/gpx-map" + }, + "branch": "master", + "submodules": false, + "revision": "8177e5ba1b85173a1aa75f0e265b54abb0050f62", + "url": null, + "hash": "sha256-twTkdTPSVNcpquvQgx3LkXd7lwBP4/sW2W98oO9/C+g=" + }, + "haumea": { + "type": "Git", + "repository": { + "type": "Git", + "url": "https://git.zaphyra.eu/haumea" + }, + "branch": "main", + "submodules": false, + "revision": "d8d558fc6dadc561381e06b0411b5a57a08927df", + "url": null, + "hash": "sha256-cKGfYcBhYShBioa1yjE5OPQtwKpCjEAOhtowhUzsrgk=" + }, + "lanzaboote": { + "type": "GitRelease", + "repository": { + "type": "GitHub", + "owner": "nix-community", + "repo": "lanzaboote" + }, + "pre_releases": false, + "version_upper_bound": null, + "release_prefix": null, + "submodules": false, + "version": "v1.0.0", + "revision": "2fe211d9c0e2320ce23dc995a3f93666ca149d9a", + "url": "https://api.github.com/repos/nix-community/lanzaboote/tarball/v1.0.0", + "hash": "sha256-RJmgVDzjRI18BWVogG6wpsl1UCuV6ui8qr4DJ1LfWZ8=" + }, + "lixModule": { + "type": "GitRelease", + "repository": { + "type": "Forgejo", + "server": "https://git.lix.systems/", + "owner": "lix-project", + "repo": "nixos-module" + }, + "pre_releases": false, + "version_upper_bound": null, + "release_prefix": null, + "submodules": false, + "version": "2.93.1", + "revision": "c3c78a32273e89d28367d8605a4c880f0b6607e3", + "url": "https://git.lix.systems/api/v1/repos/lix-project/nixos-module/archive/2.93.1.tar.gz", + "hash": "sha256-EfA5K5EZAnspmraJrXQlziffVpaT+QDBiE6yKmuaNNQ=" + }, + "mqttWebUI": { + "type": "Git", + "repository": { + "type": "Git", + "url": "http://git.zaphyra.eu/mqtt-webui" + }, + "branch": "main", + "submodules": false, + "revision": "e881d1c17d7dd55f5e0b3d15225a80ae6efd6ce8", + "url": null, + "hash": "sha256-lcDxUxYXMp/zV3K9EzS727s5kdx0PQY7/gcmbehz/Fs=" + }, + "niri-nix": { + "type": "Git", + "repository": { + "type": "Forgejo", + "server": "https://codeberg.org/", + "owner": "BANanaD3V", + "repo": "niri-nix" + }, + "branch": "main", + "submodules": false, + "revision": "0a5220500f2764422278f07f29576731e85b1d6c", + "url": "https://codeberg.org/BANanaD3V/niri-nix/archive/0a5220500f2764422278f07f29576731e85b1d6c.tar.gz", + "hash": "sha256-VACAF2i/81+d6NPO/WYpAgv0aD+hhctm1uYRsioALgc=" + }, + "nixMaid": { + "type": "Git", + "repository": { + "type": "GitHub", + "owner": "viperML", + "repo": "nix-maid" + }, + "branch": "master", + "submodules": false, + "revision": "993d95a0a5b2f4b06faa022b32112b699ca3bbfd", + "url": "https://github.com/viperML/nix-maid/archive/993d95a0a5b2f4b06faa022b32112b699ca3bbfd.tar.gz", + "hash": "sha256-pLCCqY/0JC7QdWqyEyDEvGl63f9aS8zrFLVJW7gUMKI=" + }, + "nixStd": { + "type": "Git", + "repository": { + "type": "GitHub", + "owner": "chessai", + "repo": "nix-std" + }, + "branch": "master", + "submodules": false, + "revision": "31bbc925750cc9d8f828fe55cee1a2bd985e0c00", + "url": "https://github.com/chessai/nix-std/archive/31bbc925750cc9d8f828fe55cee1a2bd985e0c00.tar.gz", + "hash": "sha256-e+7MJF2gsgTBuOWv4mCimSP0D9+naeFSw9a7N3yEmv4=" + }, + "nixosHardware": { + "type": "Git", + "repository": { + "type": "GitHub", + "owner": "NixOS", + "repo": "nixos-hardware" + }, + "branch": "master", + "submodules": false, + "revision": "8792fab9d4a6454a9201675f01326f827ce35ead", + "url": "https://github.com/NixOS/nixos-hardware/archive/8792fab9d4a6454a9201675f01326f827ce35ead.tar.gz", + "hash": "sha256-u73aVD/lUmmT3JV+kPDztl7zPwQKd0eobD1AbJltaGs=" + }, + "nixpkgs": { + "type": "Git", + "repository": { + "type": "GitHub", + "owner": "NixOS", + "repo": "nixpkgs" + }, + "branch": "nixos-25.11", + "submodules": false, + "revision": "d7a713c0b7e47c908258e71cba7a2d77cc8d71d5", + "url": "https://github.com/NixOS/nixpkgs/archive/d7a713c0b7e47c908258e71cba7a2d77cc8d71d5.tar.gz", + "hash": "sha256-6xWoytx8jFW4PF1GjRm/i/53trbpKGfz6zjzQGBr4cI=" + }, + "nixpkgsUnstable": { + "type": "Git", + "repository": { + "type": "GitHub", + "owner": "NixOS", + "repo": "nixpkgs" + }, + "branch": "master", + "submodules": false, + "revision": "f04d3629e8772518f1005264b1a43a755fbcaaee", + "url": "https://github.com/NixOS/nixpkgs/archive/f04d3629e8772518f1005264b1a43a755fbcaaee.tar.gz", + "hash": "sha256-4q5WptkoeNJDsSWbzWxShJdSgL/QgZBu//+PqEsnm9A=" + }, + "oeffisearch": { + "type": "Git", + "repository": { + "type": "Git", + "url": "https://git.zaphyra.eu/oeffisearch" + }, + "branch": "main", + "submodules": false, + "revision": "8b2131d7c60846394b735c28b968536236283709", + "url": null, + "hash": "sha256-aV01KT7uhiZ4oYzOZ4BROEvlw30isFZ5ujJSppQUOTM=" + }, + "preservation": { + "type": "Git", + "repository": { + "type": "GitHub", + "owner": "nix-community", + "repo": "preservation" + }, + "branch": "main", + "submodules": false, + "revision": "93416f4614ad2dfed5b0dcf12f27e57d27a5ab11", + "url": "https://github.com/nix-community/preservation/archive/93416f4614ad2dfed5b0dcf12f27e57d27a5ab11.tar.gz", + "hash": "sha256-mMI9IanU+Xw+pVogD2oT0I2kTmvz2Un/Apc5+CwUpEY=" + }, + "simpleNixosMailserver": { + "type": "Git", + "repository": { + "type": "GitLab", + "repo_path": "simple-nixos-mailserver/nixos-mailserver", + "server": "https://gitlab.com/" + }, + "branch": "nixos-25.11", + "submodules": false, + "revision": "25e6dbb8fca3b6e779c5a46fd03bd760b2165bb5", + "url": "https://gitlab.com/api/v4/projects/simple-nixos-mailserver%2Fnixos-mailserver/repository/archive.tar.gz?sha=25e6dbb8fca3b6e779c5a46fd03bd760b2165bb5", + "hash": "sha256-QHzRqq6gh+t3F/QU9DkP7X63dDDcuIQmaDz12p7ANTg=" + }, + "sopsNix": { + "type": "Git", + "repository": { + "type": "GitHub", + "owner": "Mic92", + "repo": "sops-nix" + }, + "branch": "master", + "submodules": false, + "revision": "c591bf665727040c6cc5cb409079acb22dcce33c", + "url": "https://github.com/Mic92/sops-nix/archive/c591bf665727040c6cc5cb409079acb22dcce33c.tar.gz", + "hash": "sha256-VfGRo1qTBKOe3s2gOv8LSoA6Fk19PvBlwQ1ECN0Evn8=" + }, + "stagit": { + "type": "Git", + "repository": { + "type": "Git", + "url": "https://git.zaphyra.eu/stagit" + }, + "branch": "main", + "submodules": false, + "revision": "e9369c19db7b6fd3d51b2dc938ad40cb5e2a1e7c", + "url": null, + "hash": "sha256-nZrulc0irdVDkZqTUl9EnuER92aE1apMIiE4aPWEUYs=" + }, + "things": { + "type": "Git", + "repository": { + "type": "Git", + "url": "https://git.zaphyra.eu/things" + }, + "branch": "main", + "submodules": false, + "revision": "c0a6f8ca7d4275cc96d1ce9723b8732f5fb4f0e4", + "url": null, + "hash": "sha256-4z5ARVj7I7KQ4HyThcBeGccuIfK3eRybjz4ctiKnu14=" + }, + "zaphyraWebsite": { + "type": "Git", + "repository": { + "type": "Git", + "url": "https://git.zaphyra.eu/website" + }, + "branch": "main", + "submodules": false, + "revision": "9808abbc409eac0f0f02cc3980a57c9207795665", + "url": null, + "hash": "sha256-81zdkclz9/h0df1bL81gFwWep1TFJSn+wYVSm6LFcoM=" + } + }, + "version": 7 +}
diff --git a/overlays/default.nix b/overlays/default.nix @@ -0,0 +1,27 @@ +{ + npins ? import ../npins, + lib ? import "${(import ../npins).nixpkgs}/lib", + ... +}: + +let + # read the current directorys files and pipe the result through a list of functions + overlays = builtins.foldl' (x: f: f x) (builtins.readDir ./.) [ + # convert to a list containing just the attribute names + (builtins.attrNames) + # drop "default.nix" from the list + (builtins.filter (name: name != "default.nix")) + # map the list to a name-value pair with a name that has the last 4 chars (".nix") stripped, and a value that is the contents of that given file + (builtins.map (name: { + inherit name; + value = import ./${name} { inherit npins; }; + })) + # convert the resulting list to an attribute set + (builtins.listToAttrs) + ]; + +in +overlays +// { + default = lib.flatten (builtins.attrValues overlays); +}
diff --git a/overlays/dnsNix/default.nix b/overlays/dnsNix/default.nix @@ -0,0 +1,8 @@ +{ + npins ? import ../../npins, + ... +}: + +final: prev: { + dnsNix = import npins.dnsNix { inherit (prev) lib; }; +}
diff --git a/overlays/nixStd/default.nix b/overlays/nixStd/default.nix @@ -0,0 +1,8 @@ +{ + npins ? import ../../npins, + ... +}: + +final: prev: { + nixStd = import npins.nixStd; +}
diff --git a/overlays/nixpkgsUnstable/default.nix b/overlays/nixpkgsUnstable/default.nix @@ -0,0 +1,11 @@ +{ + npins ? import ../../npins, + ... +}: + +final: prev: { + unstable = import npins.nixpkgsUnstable { + inherit (prev.stdenv.hostPlatform) system; + overlays = import "${npins.nixpkgsUnstable}/pkgs/top-level/impure-overlays.nix"; + }; +}
diff --git a/overlays/swaylock-plugin-fprintd/default.nix b/overlays/swaylock-plugin-fprintd/default.nix @@ -0,0 +1,18 @@ +{ ... }: + +final: prev: { + swaylock-plugin-fprintd = prev.swaylock-plugin.overrideAttrs (prevAttrs: { + src = prev.applyPatches { + src = prevAttrs.src; + patches = [ ./fprintd-support.patch ]; + postPatch = '' + substituteInPlace ./fingerprint/meson.build \ + --replace-fail "/usr" "${prev.fprintd}" + ''; + }; + + buildInputs = prevAttrs.buildInputs ++ [ + prev.dbus + ]; + }); +}
diff --git a/overlays/swaylock-plugin-fprintd/fprintd-support.patch b/overlays/swaylock-plugin-fprintd/fprintd-support.patch @@ -0,0 +1,577 @@ +From 81ae315c1f85f45a374444ed8a35a58ce1b7d50b Mon Sep 17 00:00:00 2001 +From: "Katja Ramona Sophie Kwast (zaphyra)" <git@zaphyra.eu> +Date: Tue, 26 Aug 2025 11:14:52 +0200 +Subject: [PATCH] + `https://github.com/swaywm/swaylock/compare/master...SL-RU:swaylock-fprintd:fprintd + adapted` for swaylock-plugin + +--- + completions/bash/swaylock | 2 + + completions/fish/swaylock.fish | 1 + + completions/zsh/_swaylock | 1 + + fingerprint/fingerprint.c | 251 +++++++++++++++++++++++++++++++++ + fingerprint/fingerprint.h | 43 ++++++ + fingerprint/meson.build | 33 +++++ + include/swaylock.h | 3 + + main.c | 32 ++++- + meson.build | 2 + + render.c | 2 + + 10 files changed, 369 insertions(+), 1 deletion(-) + create mode 100644 fingerprint/fingerprint.c + create mode 100644 fingerprint/fingerprint.h + create mode 100644 fingerprint/meson.build + +diff --git a/completions/bash/swaylock b/completions/bash/swaylock +index f8f411f..d13c679 100644 +--- a/completions/bash/swaylock ++++ b/completions/bash/swaylock +@@ -14,6 +14,7 @@ _swaylock-plugin() + -F + -h + -i ++ -p + -k + -K + -L +@@ -41,6 +42,7 @@ _swaylock-plugin() + --hide-keyboard-layout + --ignore-empty-password + --image ++ --fingerprint + --indicator-caps-lock + --indicator-idle-visible + --indicator-radius +diff --git a/completions/fish/swaylock.fish b/completions/fish/swaylock.fish +index 320ed6b..4ed4e62 100644 +--- a/completions/fish/swaylock.fish ++++ b/completions/fish/swaylock.fish +@@ -14,6 +14,7 @@ complete -c swaylock-plugin -l help -s h --description "Show h + complete -c swaylock-plugin -l hide-keyboard-layout -s K --description "Hide the current xkb layout while typing." + complete -c swaylock-plugin -l ignore-empty-password -s e --description "When an empty password is provided, do not validate it." + complete -c swaylock-plugin -l image -s i --description "Display the given image, optionally only on the given output." ++complete -c swaylock-plugin -l fingerprint -s p --description "Enable fingerprint scanning. Fprint is required." + complete -c swaylock-plugin -l indicator-caps-lock -s l --description "Show the current Caps Lock state also on the indicator." + complete -c swaylock-plugin -l indicator-idle-visible --description "Sets the indicator to show even if idle." + complete -c swaylock-plugin -l indicator-radius --description "Sets the indicator radius." +diff --git a/completions/zsh/_swaylock b/completions/zsh/_swaylock +index 3854ced..1a659a0 100644 +--- a/completions/zsh/_swaylock ++++ b/completions/zsh/_swaylock +@@ -18,6 +18,7 @@ _arguments -s \ + '(--hide-keyboard-layout -K)'{--hide-keyboard-layout,-K}'[Hide the current xkb layout while typing]' \ + '(--ignore-empty-password -e)'{--ignore-empty-password,-e}'[When an empty password is provided, do not validate it]' \ + '(--image -i)'{--image,-i}'[Display the given image, optionally only on the given output]:filename:_files' \ ++ '(--fingerprint -p)'{--fingerprint,-p}'[Enable fingerprint scanning. Fprint is required]' \ + '(--indicator-caps-lock -l)'{--indicator-caps-lock,-l}'[Show the current Caps Lock state also on the indicator]' \ + '(--indicator-idle-visible)'--indicator-idle-visible'[Sets the indicator to show even if idle]' \ + '(--indicator-radius)'--indicator-radius'[Sets the indicator radius]:radius:' \ +diff --git a/fingerprint/fingerprint.c b/fingerprint/fingerprint.c +new file mode 100644 +index 0000000..4e9477a +--- /dev/null ++++ b/fingerprint/fingerprint.c +@@ -0,0 +1,251 @@ ++/* ++ * Based on fprintd util to verify a fingerprint ++ * Copyright (C) 2008 Daniel Drake <dsd@gentoo.org> ++ * Copyright (C) 2020 Marco Trevisan <marco.trevisan@canonical.com> ++ * Copyright (C) 2023 Alexandr Lutsai <s.lyra@ya.ru> ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License along ++ * with this program; if not, write to the Free Software Foundation, Inc., ++ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ++ */ ++ ++#include <stdbool.h> ++#include <stdio.h> ++#include <stdlib.h> ++#include <string.h> ++#include <locale.h> ++#include <gio/gio.h> ++ ++#include "fingerprint.h" ++#include "log.h" ++ ++static void display_message(struct FingerprintState *state, const char *fmt, ...) { ++ va_list(args); ++ va_start(args, fmt); ++ vsnprintf(state->status, sizeof(state->status), fmt, args); ++ va_end(args); ++ ++ state->sw_state->auth_state = AUTH_STATE_FINGERPRINT; ++ state->sw_state->fingerprint_msg = state->status; ++ damage_state(state->sw_state); ++ schedule_auth_idle(state->sw_state); ++} ++ ++static void create_manager(struct FingerprintState *state) { ++ g_autoptr(GError) error = NULL; ++ state->connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); ++ if (state->connection == NULL) { ++ swaylock_log(LOG_ERROR, "Failed to connect to session bus: %s", error->message); ++ display_message(state, "Fingerprint error"); ++ return; ++ } ++ ++ state->manager = fprint_dbus_manager_proxy_new_sync( ++ state->connection, ++ G_DBUS_PROXY_FLAGS_NONE, ++ "net.reactivated.Fprint", ++ "/net/reactivated/Fprint/Manager", ++ NULL, &error); ++ if (state->manager == NULL) { ++ swaylock_log(LOG_ERROR, "Failed to get Fprintd manager: %s", error->message); ++ display_message(state, "Fingerprint error"); ++ return; ++ } ++ ++ swaylock_log(LOG_DEBUG, "FPrint manager created"); ++} ++ ++static void open_device(struct FingerprintState *state) { ++ state->device = NULL; ++ g_autoptr(FprintDBusDevice) dev = NULL; ++ g_autoptr(GError) error = NULL; ++ g_autofree char *path = NULL; ++ if (!fprint_dbus_manager_call_get_default_device_sync(state->manager, &path, NULL, &error)) { ++ swaylock_log(LOG_ERROR, "Impossible to verify: %s", error->message); ++ display_message(state, "Fingerprint error"); ++ return; ++ } ++ ++ swaylock_log(LOG_DEBUG, "Fingerprint: using device %s", path); ++ dev = fprint_dbus_device_proxy_new_sync( ++ state->connection, ++ G_DBUS_PROXY_FLAGS_NONE, ++ "net.reactivated.Fprint", ++ path, NULL, &error); ++ if (error) { ++ swaylock_log(LOG_ERROR, "failed to connect to device: %s", error->message); ++ display_message(state, "Fingerprint error"); ++ return; ++ } ++ ++ if (!fprint_dbus_device_call_claim_sync(dev, "", NULL, &error)) { ++ // we need to wait while device can be claimed ++ swaylock_log(LOG_DEBUG, "failed to claim the device: %s(%d)", error->message, error->code); ++ return; ++ } ++ ++ swaylock_log(LOG_DEBUG, "FPrint device opened %s", path); ++ state->device = g_steal_pointer (&dev); ++} ++ ++static void verify_result(GObject *object, const char *result, gboolean done, void *user_data) { ++ struct FingerprintState *state = user_data; ++ swaylock_log(LOG_INFO, "Verify result: %s (%s)", result, done ? "done" : "not done"); ++ state->match = g_str_equal (result, "verify-match"); ++ if (g_str_equal (result, "verify-retry-scan")) { ++ display_message(state, "Retry"); ++ return; ++ } else if(g_str_equal (result, "verify-swipe-too-short")) { ++ display_message(state, "Retry, too short"); ++ return; ++ } else if(g_str_equal (result, "verify-finger-not-centered")) { ++ display_message(state, "Retry, not centered"); ++ return; ++ } else if(g_str_equal (result, "verify-remove-and-retry")) { ++ display_message(state, "Remove and retry"); ++ return; ++ } ++ ++ if(state->match) { ++ display_message(state, "Fingerprint OK"); ++ } else { ++ display_message(state, "Retry"); ++ } ++ ++ state->completed = TRUE; ++ g_autoptr(GError) error = NULL; ++ if (!fprint_dbus_device_call_verify_stop_sync(state->device, NULL, &error)) { ++ swaylock_log(LOG_ERROR, "VerifyStop failed: %s", error->message); ++ display_message(state, "Fingerprint error"); ++ return; ++ } ++} ++ ++static void verify_started_cb(GObject *obj, GAsyncResult *res, gpointer user_data) { ++ struct FingerprintState *state = user_data; ++ if (!fprint_dbus_device_call_verify_start_finish(FPRINT_DBUS_DEVICE(obj), res, &state->error)) { ++ return; ++ } ++ ++ swaylock_log(LOG_DEBUG, "Verify started!"); ++ state->started = TRUE; ++} ++ ++static void proxy_signal_cb(GDBusProxy *proxy, ++ const gchar *sender_name, ++ const gchar *signal_name, ++ GVariant *parameters, ++ gpointer user_data) ++{ ++ struct FingerprintState *state = user_data; ++ if (!state->started) { ++ return; ++ } ++ ++ if (!g_str_equal(signal_name, "VerifyStatus")) { ++ return; ++ } ++ ++ const gchar *result; ++ gboolean done; ++ g_variant_get(parameters, "(&sb)", &result, &done); ++ verify_result(G_OBJECT (proxy), result, done, user_data); ++} ++ ++static void start_verify(struct FingerprintState *state) { ++ /* This one is funny. We connect to the signal immediately to avoid ++ * race conditions. However, we must ignore any authentication results ++ * that happen before our start call returns. ++ * This is because the verify call itself may internally try to verify ++ * against fprintd (possibly using a separate account). ++ * ++ * To do so, we *must* use the async version of the verify call, as the ++ * sync version would cause the signals to be queued and only processed ++ * after it returns. ++ */ ++ fprint_dbus_device_call_verify_start(state->device, "any", NULL, ++ verify_started_cb, ++ state); ++ ++ /* Wait for verify start while discarding any VerifyStatus signals */ ++ while (!state->started && !state->error) { ++ g_main_context_iteration(NULL, TRUE); ++ } ++ ++ if (state->error) { ++ swaylock_log(LOG_ERROR, "VerifyStart failed: %s", state->error->message); ++ display_message(state, "Fingerprint error"); ++ g_clear_error(&state->error); ++ return; ++ } ++} ++ ++static void release_callback(GObject *source_object, GAsyncResult *res, ++ gpointer user_data) { ++} ++ ++void fingerprint_init(struct FingerprintState *fingerprint_state, ++ struct swaylock_state *swaylock_state) { ++ memset(fingerprint_state, 0, sizeof(struct FingerprintState)); ++ fingerprint_state->sw_state = swaylock_state; ++ create_manager(fingerprint_state); ++ if(fingerprint_state->manager == NULL || fingerprint_state->connection == NULL) { ++ return; ++ } ++} ++ ++int fingerprint_verify(struct FingerprintState *fingerprint_state) { ++ /* VerifyStatus signals are processing, do not wait for completion. */ ++ g_main_context_iteration (NULL, FALSE); ++ if (fingerprint_state->manager == NULL || ++ fingerprint_state->connection == NULL) { ++ return false; ++ } ++ ++ if (fingerprint_state->device == NULL) { ++ open_device(fingerprint_state); ++ if (fingerprint_state->device == NULL) { ++ return false; ++ } ++ ++ g_signal_connect (fingerprint_state->device, "g-signal", G_CALLBACK (proxy_signal_cb), ++ fingerprint_state); ++ start_verify(fingerprint_state); ++ } ++ ++ ++ ++ if (!fingerprint_state->completed) { ++ return false; ++ } ++ ++ if (!fingerprint_state->match) { ++ fingerprint_state->completed = 0; ++ fingerprint_state->match = 0; ++ start_verify(fingerprint_state); ++ return false; ++ } ++ ++ return true; ++} ++ ++void fingerprint_deinit(struct FingerprintState *fingerprint_state) { ++ if (!fingerprint_state->device) { ++ return; ++ } ++ ++ g_signal_handlers_disconnect_by_func(fingerprint_state->device, proxy_signal_cb, ++ fingerprint_state); ++ fprint_dbus_device_call_release(fingerprint_state->device, NULL, release_callback, NULL); ++ fingerprint_state->device = NULL; ++} +diff --git a/fingerprint/fingerprint.h b/fingerprint/fingerprint.h +new file mode 100644 +index 0000000..d030a43 +--- /dev/null ++++ b/fingerprint/fingerprint.h +@@ -0,0 +1,43 @@ ++/* ++ * Copyright (C) 2023 Alexandr Lutsai <s.lyra@ya.ru> ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License along ++ * with this program; if not, write to the Free Software Foundation, Inc., ++ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ++ */ ++ ++#ifndef _FINGERPRINT_H ++#define _FINGERPRINT_H ++ ++#include "swaylock.h" ++#include "fingerprint/fprintd-dbus.h" ++ ++struct FingerprintState { ++ GError *error; ++ gboolean started; ++ gboolean completed; ++ gboolean match; ++ ++ char status[128]; ++ ++ FprintDBusManager *manager; ++ GDBusConnection *connection; ++ FprintDBusDevice *device; ++ struct swaylock_state *sw_state; ++}; ++ ++void fingerprint_init(struct FingerprintState *fingerprint_state, struct swaylock_state *state); ++int fingerprint_verify(struct FingerprintState *fingerprint_state); ++void fingerprint_deinit(struct FingerprintState *fingerprint_state); ++ ++#endif +\ No newline at end of file +diff --git a/fingerprint/meson.build b/fingerprint/meson.build +new file mode 100644 +index 0000000..2239952 +--- /dev/null ++++ b/fingerprint/meson.build +@@ -0,0 +1,33 @@ ++gnome = import('gnome') ++ ++dbus = dependency('dbus-1') ++glib = dependency('glib-2.0', version: '>=2.64.0') ++gio_dep = dependency('gio-2.0') ++ ++fprintd_dbus_interfaces = files( ++ '/usr/share/dbus-1/interfaces/net.reactivated.Fprint.Manager.xml', ++ '/usr/share/dbus-1/interfaces/net.reactivated.Fprint.Device.xml', ++) ++ ++fprintd_dbus_sources = gnome.gdbus_codegen('fprintd-dbus', ++ sources: fprintd_dbus_interfaces, ++ autocleanup: 'all', ++ interface_prefix: 'net.reactivated.Fprint.', ++ namespace: 'FprintDBus', ++ object_manager: true, ++) ++ ++fingerprint = declare_dependency( ++ include_directories: [ ++ include_directories('..'), ++ ], ++ sources: [ ++ fprintd_dbus_sources, ++ 'fingerprint.c' , ++ ], ++ dependencies: [ ++ gio_dep, ++ glib, ++ dbus ++ ], ++) +\ No newline at end of file +diff --git a/include/swaylock.h b/include/swaylock.h +index 233c663..6d7bf6d 100644 +--- a/include/swaylock.h ++++ b/include/swaylock.h +@@ -18,6 +18,7 @@ enum auth_state { + AUTH_STATE_IDLE, // nothing happening + AUTH_STATE_VALIDATING, // currently validating password + AUTH_STATE_INVALID, // displaying message: password was wrong ++ AUTH_STATE_FINGERPRINT, + }; + + // Indicator state: status of password buffer / typing letters +@@ -74,6 +75,7 @@ struct swaylock_args { + bool daemonize; + int ready_fd; + bool indicator_idle_visible; ++ bool fingerprint; + char *plugin_command; + bool plugin_per_output; + /* negative values = no grace; unit: seconds */ +@@ -304,6 +306,7 @@ struct swaylock_state { + bool run_display, locked; + struct ext_session_lock_manager_v1 *ext_session_lock_manager_v1; + struct ext_session_lock_v1 *ext_session_lock_v1; ++ char *fingerprint_msg; + struct zxdg_output_manager_v1 *zxdg_output_manager; + struct forward_state forward; + struct swaylock_bg_server server; +diff --git a/main.c b/main.c +index 1300fc8..6cc33a5 100644 +--- a/main.c ++++ b/main.c +@@ -29,6 +29,7 @@ + #include "seat.h" + #include "swaylock.h" + #include "ext-session-lock-v1-client-protocol.h" ++#include "fingerprint/fingerprint.h" + #include "xdg-output-unstable-v1-client-protocol.h" + #include "xdg-output-unstable-v1-server-protocol.h" + #include "fullscreen-shell-unstable-v1-client-protocol.h" +@@ -876,6 +877,7 @@ static int parse_options(int argc, char **argv, struct swaylock_state *state, + {"disable-caps-lock-text", no_argument, NULL, 'L'}, + {"indicator-caps-lock", no_argument, NULL, 'l'}, + {"line-uses-inside", no_argument, NULL, 'n'}, ++ {"fingerprint", no_argument, NULL, 'p'}, + {"line-uses-ring", no_argument, NULL, 'r'}, + {"scaling", required_argument, NULL, 's'}, + {"tiling", no_argument, NULL, 't'}, +@@ -955,6 +957,8 @@ static int parse_options(int argc, char **argv, struct swaylock_state *state, + "Disable the Caps Lock text.\n" + " -l, --indicator-caps-lock " + "Show the current Caps Lock state also on the indicator.\n" ++ " -p, --fingerprint " ++ "Enable fingerprint scanning. Fprint is required.\n" + " -s, --scaling <mode> " + "Image scaling mode: stretch, fill, fit, center, tile, solid_color.\n" + " -t, --tiling " +@@ -1060,7 +1064,7 @@ static int parse_options(int argc, char **argv, struct swaylock_state *state, + optind = 1; + while (1) { + int opt_idx = 0; +- c = getopt_long(argc, argv, "c:deFfhi:kKLlnrs:tuvC:R:", long_options, ++ c = getopt_long(argc, argv, "c:deFfhi:kKLlnprs:tuvC:R:", long_options, + &opt_idx); + if (c == -1) { + break; +@@ -1104,6 +1108,11 @@ static int parse_options(int argc, char **argv, struct swaylock_state *state, + load_image(optarg, state); + } + break; ++ case 'p': ++ if(state) { ++ state->args.fingerprint = true; ++ } ++ break; + case 'k': + if (state) { + state->args.show_keyboard_layout = true; +@@ -2257,6 +2266,17 @@ void log_init(int argc, char **argv) { + swaylock_log_init(LOG_ERROR); + } + ++static void check_fingerprint(void *d) { ++ struct FingerprintState *fingerprint_state = d; ++ if (fingerprint_verify(fingerprint_state)) { ++ do_sigusr(1); ++ } else { ++ (void)write(sigusr_fds[1], NULL, 0); ++ } ++ ++ loop_add_timer(state.eventloop, 300, check_fingerprint, fingerprint_state); ++} ++ + int main(int argc, char **argv) { + /* Initially ignore SIGUSR1 + SIGUSR2 to prevent stray signals (like + * `killall -SIGUSR2`) from affecting the pw backend subprocess */ +@@ -2291,6 +2311,7 @@ int main(int argc, char **argv) { + .hide_keyboard_layout = false, + .show_failed_attempts = false, + .indicator_idle_visible = false, ++ .fingerprint = false, + .ready_fd = -1, + .plugin_command = NULL, + .grace_time = 0.0f, +@@ -2608,6 +2629,12 @@ int main(int argc, char **argv) { + sa2.sa_flags = 0; + sigaction(SIGCHLD, &sa2, NULL); + ++ struct FingerprintState fingerprint_state; ++ if(state.args.fingerprint) { ++ fingerprint_init(&fingerprint_state, &state); ++ loop_add_timer(state.eventloop, 100, check_fingerprint, &fingerprint_state); ++ } ++ + state.run_display = true; + while (state.run_display) { + errno = 0; +@@ -2624,6 +2651,9 @@ int main(int argc, char **argv) { + ext_session_lock_v1_unlock_and_destroy(state.ext_session_lock_v1); + wl_display_roundtrip(state.display); + ++ if(state.args.fingerprint) { ++ fingerprint_deinit(&fingerprint_state); ++ } + free(state.args.font); + cairo_destroy(state.test_cairo); + cairo_surface_destroy(state.test_surface); +diff --git a/meson.build b/meson.build +index e72aff7..6babe5c 100644 +--- a/meson.build ++++ b/meson.build +@@ -116,6 +116,7 @@ if logind.found() + endif + + subdir('include') ++subdir('fingerprint') + + dependencies = [ + cairo, +@@ -125,6 +126,7 @@ dependencies = [ + xkbcommon, + wayland_client, + wayland_server, ++ fingerprint, + ] + + sources = [ +diff --git a/render.c b/render.c +index 5d48017..123847b 100644 +--- a/render.c ++++ b/render.c +@@ -109,6 +109,8 @@ static bool render_frame(struct swaylock_surface *surface) { + if (state->input_state == INPUT_STATE_CLEAR) { + // This message has highest priority + text = "Cleared"; ++ } else if (state->auth_state == AUTH_STATE_FINGERPRINT) { ++ text = state->fingerprint_msg; + } else if (state->auth_state == AUTH_STATE_VALIDATING) { + text = "Verifying"; + } else if (state->auth_state == AUTH_STATE_INVALID) { +-- +2.50.1 +
diff --git a/overlays/tuigreet/default.nix b/overlays/tuigreet/default.nix @@ -0,0 +1,20 @@ +{ ... }: + +final: prev: { + tuigreet = prev.tuigreet.overrideAttrs rec { + version = "0.10.0-unstable-2024-11-12"; + useFetchCargoVendor = true; + # for some stupid reason `cargoHash` doesn't work in `overrideAttrs`, so we have to do it this way + cargoDeps = final.rustPlatform.fetchCargoVendor { + inherit src; + hash = "sha256-FrWDRsYhfq46wBm7F0Tifiw5oGXzSgwZC05ndNXcg8k="; + }; + src = prev.fetchFromGitHub { + owner = "apognu"; + repo = "tuigreet"; + rev = "2aeca1b63dec977fc4e2ac6f97432386bedbc546"; + sha256 = "sha256-6hVTU575tP+bPAUZlGhDwvBTVISyORC0wqljC7guZdA="; + }; + doCheck = false; + }; +}
diff --git a/overlays/waylogout/default.nix b/overlays/waylogout/default.nix @@ -0,0 +1,13 @@ +{ ... }: + +final: prev: { + waylogout = prev.waylogout.overrideAttrs rec { + version = "0.3"; + src = prev.fetchFromGitHub { + owner = "loserMcloser"; + repo = "waylogout"; + rev = "v${version}"; + hash = "sha256-dsuuTjmZm3IpqXU68LsAz86HNbMFvKhWPYOMG/5Z4jE="; + }; + }; +}
diff --git a/overlays/zphaLib/default.nix b/overlays/zphaLib/default.nix @@ -0,0 +1,15 @@ +{ + npins ? import ../../npins, + ... +}: + +final: prev: { + lib = prev.lib.extend ( + _: _: { + zpha = import ../../lib { + inherit npins; + lib = prev.lib; + }; + } + ); +}
diff --git a/overlays/zphaPackages/default.nix b/overlays/zphaPackages/default.nix @@ -0,0 +1,30 @@ +{ + npins ? import ../../npins, + ... +}: + +final: prev: { + zpha = prev.lib.mergeAttrsList [ + (import ../../packages prev) + { + website = prev.callPackage "${npins.zaphyraWebsite}/package.nix" { }; + + flauschehorn-sexy = prev.callPackage "${npins.flauschehornSexy}/package.nix" { }; + things = prev.callPackage "${npins.things}/package.nix" { }; + stagit = prev.callPackage "${npins.stagit}/package.nix" { }; + oeffisearch = prev.callPackage "${npins.oeffisearch}/package.nix" { + version = builtins.substring 0 6 npins.oeffisearch.revision; + commit = npins.oeffisearch.revision; + }; + + mqtt-webui = prev.callPackage "${npins.mqttWebUI}/nix/package.nix" { }; + + # gpxMap + datamaps = prev.callPackage "${npins.gpxMap}/nix/packages/datamaps/package.nix" { }; + gpx-map = prev.callPackage "${npins.gpxMap}/nix/packages/gpx-map/package.nix" { }; + generateTilesFromGPX = + prev.callPackage "${npins.gpxMap}/nix/packages/generateTilesFromGPX/package.nix" + { }; + } + ]; +}
diff --git a/packages/adwaita-colors-icon-theme/package.nix b/packages/adwaita-colors-icon-theme/package.nix @@ -0,0 +1,69 @@ +{ + lib, + stdenvNoCC, + fetchFromGitHub, + gtk3, + hicolor-icon-theme, + adwaita-icon-theme, + ... +}: + +stdenvNoCC.mkDerivation (finalAttrs: { + pname = "adwaita-colors-icon-theme"; + version = "2.4.1"; + rev = "v${finalAttrs.version}"; + srcHash = "sha256-M5dFb759sXfpD9/gQVF3sngyW4WdSgy4usInds9VIWk="; + + src = fetchFromGitHub { + owner = "dpejoh"; + repo = "Adwaita-colors"; + hash = finalAttrs.srcHash; + inherit (finalAttrs) rev; + }; + + nativeBuildInputs = [ + gtk3 + ]; + + propagatedBuildInputs = [ + hicolor-icon-theme + adwaita-icon-theme + ]; + + patchPhase = '' + # for some reason there are fucking broken symlinks?! + rm ./Adwaita-blue/scalable/places/com.bitwig.BitwigStudio.application-bitwig-project-folder-legacy.svg + + find ./ -type d -name "Adwaita-*" | while read -r dir; do + # Construct the full path to the "index.theme" file + file="$dir/index.theme" + # Check if the file exists and contains the target text + if [ -f "$file" ]; then + substituteInPlace $file \ + --replace-fail "Hidden=true" "" \ + --replace-fail "AdwaitaLegacy," "" + fi + done + ''; + + installPhase = '' + runHook preInstall + + install -d $out/share/icons + cp -r Adwaita-* $out/share/icons/ + + gtk-update-icon-cache -f -t $out/share/icons/Adwaita* + + runHook postInstall + ''; + + dontDropIconThemeCache = true; + + meta = { + description = "Adwaita Colors customizes Adwaita icons to match your GNOME theme's accent color, providing a cohesive, personalized look."; + homepage = "https://github.com/dpejoh/Adwaita-colors"; + license = with lib.licenses; [ gpl3Only ]; + platforms = lib.platforms.linux; + maintainers = with lib.maintainers; [ zaphyra ]; + }; +})
diff --git a/packages/chaosctl/chaosctl.fish b/packages/chaosctl/chaosctl.fish @@ -0,0 +1,65 @@ +# GLOBALS +set CMD "$argv[1]" +set CMDS beamer door help tv + +function error + echo $argv[2..] 1>&2 + return $argv[1] +end + +set _descr_beamer "Control the beamer in the lounge" +set _usage_beamer "<on|off>" +function cmd_beamer + switch $argv[1] + case on + ssh chaos@lounge.cccda.de beamer-on + case off + ssh chaos@lounge.cccda.de beamer-off + case "*" + error 1 "$(cmd_help beamer)" + end +end + +set _descr_door "Control the door" +function cmd_door + ssh door@door.cccda.de $argv +end + +set _descr_help "Display this message" +function cmd_help + set cmd "$argv[1]" + if [ "$cmd" != "" ] + echo "\ +$(eval echo \$_descr_$cmd) + +Usage: + chaosctl $cmd $(eval echo \$_usage_$cmd)" + else + echo "\ +The chaosctl utility + +Usage: + chaosctl <command> + +COMMANDS" + for cmd in $CMDS + echo -e " $cmd\t$(eval echo \$_descr_$cmd)" + end + end +end + +set _descr_tv "\tControl the TV in the kitchen" +function cmd_tv + ssh chaos@kitchen.cccda.de tv $argv +end + +if [ "cmd_$CMD" = cmd_ ] + error 1 "$(cmd_help)" + exit $status +end + +if type -q "cmd_$CMD" + cmd_$CMD $argv[2..] +else + error 127 "No such subcommand '$CMD'" +end
diff --git a/packages/chaosctl/package.nix b/packages/chaosctl/package.nix @@ -0,0 +1,5 @@ +{ + writers, + ... +}: +writers.writeFishBin "chaosctl" ./chaosctl.fish
diff --git a/packages/default.nix b/packages/default.nix @@ -0,0 +1,6 @@ +pkgs: + +builtins.removeAttrs (pkgs.lib.packagesFromDirectoryRecursive { + directory = ./.; + callPackage = pkgs.callPackage; +}) [ "default" ]
diff --git a/packages/deployrsActivator/package.nix b/packages/deployrsActivator/package.nix @@ -0,0 +1,53 @@ +{ + systemConfig ? null, + lib, + buildEnv, + writeTextFile, + writeScriptBin, + runtimeShell, + deploy-rs, +}: + +if systemConfig != null then + buildEnv { + name = "activatable-${systemConfig.system.build.toplevel.name}"; + paths = [ + systemConfig.system.build.toplevel + (writeTextFile { + name = "${systemConfig.system.build.toplevel.name}-activate-path"; + text = '' + #!${runtimeShell} + set -euo pipefail + + if [[ "''${DRY_ACTIVATE:-}" == "1" ]] + then + $PROFILE/bin/switch-to-configuration dry-activate + elif [[ "''${BOOT:-}" == "1" ]] + then + $PROFILE/bin/switch-to-configuration boot + else + # work around https://github.com/NixOS/nixpkgs/issues/73404 + cd /tmp + + $PROFILE/bin/switch-to-configuration switch + + # https://github.com/serokell/deploy-rs/issues/31 + ${lib.optionalString systemConfig.boot.loader.systemd-boot.enable "sed -i '/^default /d' ${systemConfig.boot.loader.efi.efiSysMountPoint}/loader/loader.conf"} + fi + ''; + executable = true; + destination = "/deploy-rs-activate"; + }) + (writeTextFile { + name = "${systemConfig.system.build.toplevel.name}-activate-rs"; + destination = "/activate-rs"; + executable = true; + text = '' + #!${runtimeShell} + exec ${deploy-rs}/bin/activate "$@" + ''; + }) + ]; + } +else + writeScriptBin "activate" "echo 'No system config given!'"
diff --git a/packages/domsonic/package.nix b/packages/domsonic/package.nix @@ -0,0 +1,41 @@ +{ + lib, + stdenv, + fetchgit, + fetchYarnDeps, + yarnConfigHook, + yarnBuildHook, + yarnInstallHook, + nodejs, +}: + +stdenv.mkDerivation (finalAttrs: { + pname = "domsonic"; + version = builtins.substring 0 6 finalAttrs.rev; + rev = "1a49015b466af151e47bf1bf3dec971a03429ea4"; + + src = fetchgit { + url = "https://git.zaphyra.eu/domsonic"; + hash = "sha256-lsrVnTYsltaBPAvfvPrqJYX8+ky9ce0KPBgOM/c5lc4="; + inherit (finalAttrs) rev; + }; + + yarnOfflineCache = fetchYarnDeps { + yarnLock = finalAttrs.src + "/yarn.lock"; + hash = "sha256-A3zfYLa/UMIUR1xig3t7xQbx5nmx8Fwdqq2EFHCgSbM="; + }; + + nativeBuildInputs = [ + nodejs + yarnConfigHook + yarnBuildHook + yarnInstallHook + ]; + + installPhase = '' + cp -r ./dist $out + ''; + + meta.maintainers = with lib.maintainers; [ zaphyra ]; + +})
diff --git a/packages/dssd/package.nix b/packages/dssd/package.nix @@ -0,0 +1,46 @@ +{ + lib, + rustPlatform, + fetchFromGitHub, + pkg-config, + dbus, + ... +}: + +rustPlatform.buildRustPackage (finalAttrs: { + pname = "dssd"; + version = "0.3.3"; + rev = "v${finalAttrs.version}"; + srcHash = "sha256-gAV4gwrfvYfc2f1tDY/cNOFMrQzrzHSmEFsKg7ke/6c="; + cargoHash = "sha256-yX2/2TW3FNbqwzR6+5yP26E2Eps0bTJgJJrDIQG2KQU="; + + src = fetchFromGitHub { + owner = "ylxdzsw"; + repo = finalAttrs.pname; + hash = finalAttrs.srcHash; + inherit (finalAttrs) rev; + }; + + nativeBuildInputs = [ + pkg-config + ]; + + buildInputs = [ + dbus + ]; + + postInstall = '' + mkdir -p $out/share/dbus-1/services + + cp ./org.freedesktop.secrets.service $out/share/dbus-1/services/ + ''; + + meta = { + mainProgram = "dssd"; + description = "Dead Simple Secret Daemon"; + homepage = "https://github.com/ylxdzsw/dssd"; + license = [ lib.licenses.mit ]; + platforms = lib.platforms.linux; + maintainers = with lib.maintainers; [ zaphyra ]; + }; +})
diff --git a/packages/echo-meter/package.nix b/packages/echo-meter/package.nix @@ -0,0 +1,56 @@ +{ + lib, + fetchFromGitHub, + stdenv, + pkg-config, + gtk4, + gtk4-layer-shell, + json-glib, + ... +}: + +stdenv.mkDerivation (finalAttrs: { + pname = "echo-meter"; + version = "0.0.0-unstable-2025-08-26"; + rev = "e6e5a70600e85edcfca3aa44153a26614b6fe55c"; + srcHash = "sha256-QPQ3crm7i0L0PivlhIsPLrv1TNVwF/c+WQDRK4EtEV8="; + + src = fetchFromGitHub { + owner = "Nithin-3"; + repo = finalAttrs.pname; + hash = finalAttrs.srcHash; + inherit (finalAttrs) rev; + }; + + postPatch = '' + substituteInPlace src/echo-meter.c \ + --replace-fail "gtk-layer-shell/gtk-layer-shell.h" "gtk4-layer-shell/gtk4-layer-shell.h" + ''; + + nativeBuildInputs = [ + pkg-config + ]; + + buildInputs = [ + gtk4 + gtk4-layer-shell + json-glib + ]; + + installPhase = '' + mkdir -p $out/bin + + cp ./echo-meter $out/bin + cp -r ./assets $out/share + ''; + + meta = { + description = "A lightweight GTK4 layer-shell application that displays always-on-top real-time audio, brightness, and microphone level indicators."; + homepage = "https://github.com/Nithin-3/echo-meter"; + license = lib.licenses.mit; + maintainers = with lib.maintainers; [ zaphyra ]; + platforms = lib.platforms.linux; + mainProgram = "echo-meter"; + }; + +})
diff --git a/packages/gotosocial-unstable/0001-disable-template-indentation.patch b/packages/gotosocial-unstable/0001-disable-template-indentation.patch @@ -0,0 +1,40 @@ +From 319ef126ac07b7dc4d63ab749b2dac7fe155a905 Mon Sep 17 00:00:00 2001 +From: "Katja Ramona Sophie Kwast (zaphyra)" <git@zaphyra.eu> +Date: Fri, 22 Aug 2025 09:46:11 +0200 +Subject: [PATCH] disable template indentation + +--- + internal/router/template.go | 12 ++---------- + 1 file changed, 2 insertions(+), 10 deletions(-) + +diff --git a/internal/router/template.go b/internal/router/template.go +index 860f9aad5..e4d0d99f5 100644 +--- a/internal/router/template.go ++++ b/internal/router/template.go +@@ -323,21 +323,13 @@ func subtract(n1 int, n2 int) int { + // indent appropriately indents the given html + // by prepending each line with the indentStr. + func indent(n int, html template.HTML) template.HTML { +- out := indentRegex.ReplaceAllString( +- string(html), +- indents[:n*indentStrLen], +- ) +- return noescape(out) ++ return html + } + + // indentAttr appropriately indents the given html + // attribute by prepending each line with the indentStr. + func indentAttr(n int, html template.HTMLAttr) template.HTMLAttr { +- out := indentRegex.ReplaceAllString( +- string(html), +- indents[:n*indentStrLen], +- ) +- return noescapeAttr(out) ++ return html + } + + // outdentPreformatted outdents all preformatted text in +-- +2.50.1 +
diff --git a/packages/gotosocial-unstable/package.nix b/packages/gotosocial-unstable/package.nix @@ -0,0 +1,142 @@ +{ + unstable, + lib, + fetchFromGitea, + applyPatches, + mkYarnPackage, + makeWrapper, + installShellFiles, + nixosTests, + ... +}: + +unstable.buildGoModule (finalAttrs: { + pname = "gotosocial"; + version = "0.22.0-unstable-2026-05-20-${builtins.substring 0 6 finalAttrs.rev}"; + rev = "7161b5c7db457353f09a41612c4d3be3775c2636"; + srcHash = "sha256-CJfov3aMCgijlqNN8BOO8RO9IqPoyruMQQw2654pBX0="; + + src = applyPatches { + src = fetchFromGitea { + domain = "codeberg.org"; + owner = "superseriousbusiness"; + repo = "gotosocial"; + hash = finalAttrs.srcHash; + inherit (finalAttrs) rev; + }; + patches = [ + ./0001-disable-template-indentation.patch + ]; + }; + + frontend-assets = mkYarnPackage { + name = "${finalAttrs.pname}-${finalAttrs.version}-frontendAssets"; + inherit (finalAttrs) src version; + + packageJSON = "${finalAttrs.src}/web/source/package.json"; + yarnLock = "${finalAttrs.src}/web/source/yarn.lock"; + + configurePhase = '' + runHook preConfigure + + cp -r $node_modules node_modules + chmod +w -R node_modules + + runHook postConfigure + ''; + + buildPhase = '' + runHook preBuild + + mkdir -p ./web/source ./web/assets + + cp -r $src/web/source/. ./web/source + cp -r $src/web/assets/. ./web/assets + + export NODE_OPTIONS=--openssl-legacy-provider + node ./node_modules/ts-patch/bin/ts-patch.js install + yarn --offline --cwd ./web/source build + + runHook postBuild + ''; + + distPhase = "true"; + installPhase = "cp -r ./web/assets $out"; + + meta.license = lib.licenses.agpl3Only; + }; + + ldflags = [ + "-s" + "-w" + "-X 'main.Commit=${finalAttrs.rev}'" + "-X 'main.Version=${finalAttrs.version}'" + ]; + tags = [ + "netgo" + "osusergo" + "static_build" + "kvformat" + ]; + + env.CGO_ENABLED = 0; + + vendorHash = null; + + nativeBuildInputs = [ installShellFiles ]; + buildInputs = [ makeWrapper ]; + + # tests are working only on x86_64-linux + # doCheck = stdenv.hostPlatform.isLinux && stdenv.hostPlatform.isx86_64; + # checks are currently very unstable in our setup, so we should test manually for now + doCheck = false; + + checkFlags = + let + # flaky / broken tests + skippedTests = [ + # See: https://github.com/superseriousbusiness/gotosocial/issues/2651 + "TestPage/minID,_maxID_and_limit_set" + ]; + in + [ "-skip=^${builtins.concatStringsSep "$|^" skippedTests}$" ]; + + installCheckPhase = '' + runHook preCheck + + $out/bin/gotosocial --help + + runHook postCheck + ''; + + postInstall = '' + mkdir -p $out/share/${finalAttrs.pname}/web/{templates,assets} + + cp -r ./web/template/. $out/share/${finalAttrs.pname}/web/template/ + cp -rf ${finalAttrs.frontend-assets}/. $out/share/${finalAttrs.pname}/web/assets/ + + installShellCompletion --cmd gotosocial \ + --bash <($out/bin/gotosocial completion bash) \ + --fish <($out/bin/gotosocial completion fish) \ + --zsh <($out/bin/gotosocial completion zsh) + ''; + + passthru.tests.gotosocial = nixosTests.gotosocial; + + meta = with lib; { + mainProgram = "gotosocial"; + maintainers = with lib.maintainers; [ zaphyra ]; + license = licenses.agpl3Only; + homepage = "https://gotosocial.org"; + changelog = "https://codeberg.org/superseriousbusiness/gotosocial/releases"; + description = "Fast, fun, ActivityPub server, powered by Go"; + longDescription = '' + ActivityPub social network server, written in Golang. + You can keep in touch with your friends, post, read, and + share images and articles. All without being tracked or + advertised to! A light-weight alternative to Mastodon + and Pleroma, with support for clients! + ''; + }; + +})
diff --git a/packages/mautrix-telegram/package.nix b/packages/mautrix-telegram/package.nix @@ -0,0 +1,32 @@ +{ + lib, + buildGoModule, + fetchFromGitHub, +}: + +buildGoModule (finalAttrs: { + pname = "mautrix-telegramgo"; + version = "0.2605.0"; + + srcHash = "sha256-9TCXyGvFCZAv8xIUW3oiVRv5EBdObrLuALfME/oAWBE="; + vendorHash = "sha256-xcBbBIsFXQ90WyQ8OY+CCVIiBepIlOD/o+ZjabNvM0Q="; + + src = fetchFromGitHub { + owner = "mautrix"; + repo = "telegram"; + rev = "v${finalAttrs.version}"; + hash = finalAttrs.srcHash; + }; + + tags = [ "goolm" ]; + + doCheck = false; + + meta = { + homepage = "https://github.com/mautrix/telegram"; + description = "A Matrix-Telegram puppeting bridge"; + license = lib.licenses.agpl3Plus; + maintainers = with lib.maintainers; [ zaphyra ]; + mainProgram = "mautrix-telegram"; + }; +})
diff --git a/packages/memos/package.nix b/packages/memos/package.nix @@ -0,0 +1,80 @@ +{ + fetchFromGitHub, + buildGoModule, + stdenvNoCC, + nix-update-script, + nodejs, + lib, + fetchPnpmDeps, + pnpmConfigHook, + pnpm, +}: +let + version = "0.26.0"; + src = fetchFromGitHub { + owner = "usememos"; + repo = "memos"; + rev = "v${version}"; + hash = "sha256-3Q+acA1MjPlWMpRHpkNsg7CVc2DrBosfg4JC115OXMQ="; + }; + + memos-web = stdenvNoCC.mkDerivation (finalAttrs: { + pname = "memos-web"; + inherit version src; + pnpmDeps = fetchPnpmDeps { + inherit (finalAttrs) pname version src; + sourceRoot = "${finalAttrs.src.name}/web"; + fetcherVersion = 1; + hash = "sha256-6lrXpblVY7Q7UMMU0obVAwACodW+WJm/INiSMPzEgEI="; + }; + pnpmRoot = "web"; + nativeBuildInputs = [ + nodejs + pnpmConfigHook + pnpm + ]; + buildPhase = '' + runHook preBuild + pnpm -C web build + runHook postBuild + ''; + installPhase = '' + runHook preInstall + cp -r web/dist $out + runHook postInstall + ''; + }); +in +buildGoModule { + pname = "memos"; + inherit + version + src + memos-web + ; + + vendorHash = "sha256-J/qsybKA+RIKQKaHmAtTcHt6z5VnFpWqFT0dF7AceWQ="; + + doCheck = false; + + preBuild = '' + rm -rf server/router/frontend/dist + cp -r ${memos-web} server/router/frontend/dist + ''; + + passthru.updateScript = nix-update-script { + extraArgs = [ + "--subpackage" + "memos-web" + ]; + }; + + meta = { + homepage = "https://usememos.com"; + description = "Lightweight, self-hosted memo hub"; + changelog = "https://github.com/usememos/memos/releases/tag/${src.rev}"; + license = lib.licenses.mit; + mainProgram = "memos"; + maintainers = with lib.maintainers; [ zaphyra ]; + }; +}
diff --git a/packages/niri-screen-time/package.nix b/packages/niri-screen-time/package.nix @@ -0,0 +1,29 @@ +{ + lib, + buildGoModule, + fetchFromGitHub, +}: + +buildGoModule (finalAttrs: { + pname = "niri-screen-time"; + version = "0.0.0-${builtins.substring 0 6 finalAttrs.rev}"; + rev = "dbe4a93c9a163427b0f7bed96fcfb338e274499f"; + srcHash = "sha256-BxLV4cUKLb3uGLlj3Pn0PF6gAxwimHTK0WZyOYWf/L8="; + + src = fetchFromGitHub { + owner = "probeldev"; + repo = "niri-screen-time"; + hash = finalAttrs.srcHash; + inherit (finalAttrs) rev; + }; + + vendorHash = "sha256-9y1F2ZrmpiQJ9ZTq9SoRE2PxR65DDNCeBKf4M0HUQC4="; + + meta = { + homepage = "https://github.com/probeldev/niri-screen-time"; + description = "Niri Screen Time"; + license = lib.licenses.mit; + maintainers = with lib.maintainers; [ zaphyra ]; + mainProgram = "niri-screen-time"; + }; +})
diff --git a/packages/niri-taskbar/package.nix b/packages/niri-taskbar/package.nix @@ -0,0 +1,47 @@ +{ + lib, + unstable, + fetchFromGitHub, + pkg-config, + glib, + atkmm, + gdk-pixbuf, + cairo, + gtk3, + ... +}: + +unstable.rustPlatform.buildRustPackage (finalAttrs: { + pname = "niri-taskbar"; + version = "0.4.0+niri.25.11"; + rev = "v${finalAttrs.version}"; + srcHash = "sha256-aE5v94AA6bC0CP8pv/SPBxGKpkH+GxR/p7hTKXlvk3E=s"; + cargoHash = "sha256-WRc1+ZVhiIfmLHaczAPq21XudI08CgVhlIhVcf0rmSw="; + + src = fetchFromGitHub { + owner = "LawnGnome"; + repo = finalAttrs.pname; + hash = finalAttrs.srcHash; + inherit (finalAttrs) rev; + }; + + nativeBuildInputs = [ + pkg-config + ]; + + buildInputs = [ + glib + atkmm + gdk-pixbuf + cairo + gtk3 + ]; + + meta = { + description = "Niri taskbar module for Waybar"; + homepage = "https://github.com/LawnGnome/niri-taskbar"; + license = [ lib.licenses.mit ]; + platforms = lib.platforms.linux; + maintainers = with lib.maintainers; [ zaphyra ]; + }; +})
diff --git a/packages/nirilayout/package.nix b/packages/nirilayout/package.nix @@ -0,0 +1,48 @@ +{ + lib, + buildGoModule, + fetchFromGitHub, + pkg-config, + gtk4, + gtk4-layer-shell, + gobject-introspection, + ... +}: + +buildGoModule (finalAttrs: { + pname = "nirilayout"; + version = "0.2.0"; + + src = fetchFromGitHub { + owner = "calico32"; + repo = finalAttrs.pname; + hash = "sha256-rlW8n+1gVPFBgv5UbVS43bI0NBVHfOPjJ6R0kMytrcQ="; + rev = "v${finalAttrs.version}"; + }; + + vendorHash = "sha256-o6/ZH70j3e4kU/ZxP41/88kXakrWAu6QdHtOY+EceCs="; + + nativeBuildInputs = [ + pkg-config + ]; + + buildInputs = [ + gtk4 + gtk4-layer-shell + gobject-introspection + ]; + + meta = with lib; { + maintainers = with lib.maintainers; [ zaphyra ]; + license = licenses.mit; + homepage = "https://github.com/calico32/nirilayout"; + changelog = "https://github.com/calico32/nirilayout/releases/tag/v${finalAttrs.version}"; + description = "niri monitor layout switcher"; + longDescription = '' + nirilayout is a simple tool to quickly switch your niri output configuration + between different layouts. Especially useful for laptop users who move between + different setups frequently. + ''; + }; + +})
diff --git a/packages/nirimap/package.nix b/packages/nirimap/package.nix @@ -0,0 +1,44 @@ +{ + lib, + rustPlatform, + fetchFromGitHub, + pkg-config, + wrapGAppsHook4, + gtk4, + gtk4-layer-shell, + ... +}: + +rustPlatform.buildRustPackage (finalAttrs: { + pname = "nirimap"; + version = "0.2.0"; + rev = "v${finalAttrs.version}"; + srcHash = "sha256-4HnmIc9FDXgPfbJdhjuVenc2R/wZ9ULTi6QaTskO1/s="; + cargoHash = "sha256-EI79WewUTAOFivRsR2ZjywEAYZ9Lq6YnfwPml071CqU="; + + src = fetchFromGitHub { + owner = "alexandergknoll"; + repo = finalAttrs.pname; + hash = finalAttrs.srcHash; + inherit (finalAttrs) rev; + }; + + nativeBuildInputs = [ + pkg-config + wrapGAppsHook4 + ]; + + buildInputs = [ + gtk4 + gtk4-layer-shell + ]; + + meta = { + description = "A tool that automatically maximizes the only window of a niri workspace"; + homepage = "https://github.com/Antiz96/oniri"; + license = lib.licenses.gpl3; + mainProgram = "oniri"; + maintainers = with lib.maintainers; [ zaphyra ]; + platforms = lib.platforms.linux; + }; +})
diff --git a/packages/nix-cleanup/nix-cleanup.sh b/packages/nix-cleanup/nix-cleanup.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -eu + +# Delete everything from this profile that isn't currently needed +nix-env --delete-generations old + +# Delete generations older than a week +nix-collect-garbage +nix-collect-garbage --delete-older-than 7d + +# Optimize +nix-store --gc --print-dead +nix-store --optimise
diff --git a/packages/nix-cleanup/package.nix b/packages/nix-cleanup/package.nix @@ -0,0 +1,25 @@ +{ + lib, + stdenvNoCC, + ... +}: + +stdenvNoCC.mkDerivation (finalAttrs: { + pname = "nix-cleanup"; + version = "0.0.0.1"; + + dontUnpack = true; + src = ./nix-cleanup.sh; + + postInstall = '' + mkdir -p $out/bin + install --mode +x $src $out/bin/nix-cleanup + ''; + + meta = { + description = "Just a tiny helper to clean up your nix store!"; + license = [ lib.licenses.mit ]; + platforms = lib.platforms.linux; + maintainers = with lib.maintainers; [ zaphyra ]; + }; +})
diff --git a/packages/nocturne/package.nix b/packages/nocturne/package.nix @@ -0,0 +1,58 @@ +{ + lib, + fetchFromGitHub, + python3Packages, + libadwaita, + wrapGAppsHook4, + meson, + ninja, + pkg-config, + libsecret, + gtk4, + blueprint-compiler, + desktop-file-utils, +}: + +python3Packages.buildPythonApplication rec { + pname = "nocturne"; + version = "1.0.1"; + + pyproject = false; + + src = fetchFromGitHub { + owner = "Jeffser"; + repo = "Nocturne"; + tag = version; + hash = "sha256-nW8DCziEERN6xamT+eS6eGTnoNWG6OTdgLb4E2FIzXQ="; + }; + + nativeBuildInputs = [ + wrapGAppsHook4 + meson + ninja + pkg-config + gtk4 + blueprint-compiler + desktop-file-utils + ]; + + buildInputs = [ + libadwaita + libsecret + ]; + + dontWrapGApps = true; + + preFixup = '' + makeWrapperArgs+=("''${gappsWrapperArgs[@]}") + ''; + + meta = { + description = " An Adwaita Music Player / Library Manager "; + changelog = "https://github.com/Jeffser/Nocturne/releases/tag/${version}"; + homepage = "https://github.com/Jeffser/Nocturne"; + license = lib.licenses.gpl3; + mainProgram = "nocturne"; + maintainers = with lib.maintainers; [ zaphyra ]; + }; +}
diff --git a/packages/oniri/package.nix b/packages/oniri/package.nix @@ -0,0 +1,35 @@ +{ + lib, + rustPlatform, + fetchFromGitHub, + pkg-config, + ... +}: + +rustPlatform.buildRustPackage (finalAttrs: { + pname = "oniri"; + version = "1.2.0"; + rev = "v${finalAttrs.version}"; + srcHash = "sha256-54OA+N2qCbtfbYAEh18D1rUjxvmarZHPaH33maQa4uc="; + cargoHash = "sha256-k1hnveoLOld7ec/FRmdUt8Ayoz45FO1KBkKVlA8E/ss="; + + src = fetchFromGitHub { + owner = "Antiz96"; + repo = finalAttrs.pname; + hash = finalAttrs.srcHash; + inherit (finalAttrs) rev; + }; + + nativeBuildInputs = [ + pkg-config + ]; + + meta = { + description = "A tool that automatically maximizes the only window of a niri workspace"; + homepage = "https://github.com/Antiz96/oniri"; + license = lib.licenses.gpl3; + mainProgram = "oniri"; + platforms = lib.platforms.linux; + maintainers = with lib.maintainers; [ zaphyra ]; + }; +})
diff --git a/packages/phanpy/package.nix b/packages/phanpy/package.nix @@ -0,0 +1,47 @@ +{ + lib, + fetchFromGitHub, + buildNpmPackage, + clientName ? "Phanpy", + website ? "https://phanpy.social", + defaultInstance ? "", + defaultInstanceRegistrationUrl ? "", + defaultLang ? "en", + ... +}: + +buildNpmPackage (finalAttrs: { + pname = "phanpy"; + version = "2025.07.18.3f4b1a6"; + + rev = "b74005d30721d55832b14a8f2e07bd918fdeb5c4"; + srcHash = "sha256-0OkH/XojM0W2oun797sNJqFrxNqFau1P+NECxCrib20="; + npmDepsHash = "sha256-2a+5G0ENpjOvw+TuxEJrkabAB3uoQnaBQc7Nek7a/dw="; + + src = fetchFromGitHub { + owner = "cheeaun"; + repo = "phanpy"; + hash = finalAttrs.srcHash; + inherit (finalAttrs) rev; + }; + + env = { + NODE_OPTIONS = "--openssl-legacy-provider"; + PHANPY_CLIENT_NAME = clientName; + PHANPY_WEBSITE = website; + PHANPY_DEFAULT_INSTANCE = defaultInstance; + PHANPY_DEFAULT_INSTANCE_REGISTRATION_URL = defaultInstanceRegistrationUrl; + PHANPY_DEFAULT_LANG = defaultLang; + }; + + installPhase = '' + cp -r ./dist $out + ''; + + meta = { + description = "A minimalistic opinionated Mastodon web client "; + homepage = "https://github.com/cheeaun/phanpy"; + license = lib.licenses.mit; + maintainers = with lib.maintainers; [ zaphyra ]; + }; +})
diff --git a/packages/setupDisk/package.nix b/packages/setupDisk/package.nix @@ -0,0 +1,108 @@ +{ + systemConfig ? null, + lib, + writeShellApplication, + writeScriptBin, + btrfs-progs, + parted, + openssh, +}: + +if systemConfig != null then + writeShellApplication { + name = "setup-disk-${systemConfig.networking.hostName}"; + runtimeInputs = [ + btrfs-progs + parted + openssh + ]; + text = + let + inherit (systemConfig.networking) hostName; + bootDisk = "/dev/disk/by-partlabel/${hostName}-boot"; + rootDisk = "/dev/disk/by-partlabel/${hostName}-root"; + subvolumes = lib.pipe systemConfig.fileSystems [ + builtins.attrValues + (builtins.filter (element: element.device == "/dev/mapper/root")) + (builtins.map (element: element.options)) + lib.flatten + (builtins.filter (value: (builtins.substring 0 6 value) == "subvol")) + (builtins.map (element: builtins.substring 7 (builtins.builtins.stringLength element) element)) + ]; + in + '' + set -euo pipefail + + read -rp 'Disk: ' disk + read -srp "LUKS passphrase: " pass1 + echo "" + read -srp 'LUKS passphrase (repeat): ' pass2 + echo "" + + if [[ "$pass1" != "$pass2" ]] + then + echo "Passphrases don't match" + exit + fi + + parted --script --align optimal --fix "$disk" -- mklabel gpt \ + mkpart ${hostName}-boot fat32 1M 1024M \ + mkpart ${hostName}-root btrfs 1025M 100% \ + set 1 esp on \ + type 2 C12A7328-F81F-11D2-BA4B-00A0C93EC93B \ + type 2 6523f8ae-3eb1-4e2a-a05a-18b695ae656f + + echo "$pass1" | cryptsetup -q luksFormat "${rootDisk}" + echo "$pass1" | cryptsetup -q luksOpen "${rootDisk}" root + + mkfs.vfat "${bootDisk}" + mkfs.btrfs /dev/mapper/root + + mount --verbose /dev/mapper/root /mnt + + ${lib.optionalString systemConfig.common.configure.persist.system.enable '' + btrfs subvolume create /mnt/nixos-root-1 + btrfs subvolume create /mnt/nixos-root-2 + btrfs subvolume create /mnt/nixos-root-3 + btrfs subvolume create /mnt/nixos-root-4 + btrfs subvolume set-default /mnt/nixos-root-1 + ''} + ${lib.optionalString (systemConfig.common.configure.persist.system.enable == false) '' + btrfs subvolume create /mnt/nixos-root + btrfs subvolume set-default /mnt/nixos-root + ''} + ${lib.optionalString (builtins.elem "swap" subvolumes) '' + btrfs subvolume create /mnt/swap + btrfs filesystem mkswapfile --size ${systemConfig.common.configure.rootDisk.swap.size} --uuid clear /mnt/swap/swapfile + ''} + ${lib.pipe subvolumes [ + (lib.remove "nixos-root-1") + (lib.remove "nixos-root") + (lib.remove "swap") + (builtins.map (element: "btrfs subvolume create /mnt/${element}")) + (builtins.concatStringsSep "\n") + ]} + + umount --verbose /mnt + + ${lib.pipe systemConfig.fileSystems [ + (lib.filterAttrs (name: value: value.device == "/dev/mapper/root")) + (lib.concatMapAttrsStringSep "\n" ( + name: value: + "mount --verbose --mkdir --options ${ + lib.pipe value.options [ + (builtins.filter (value: value != "x-initrd.mount")) + (builtins.concatStringsSep ",") + ] + } ${value.device} ${lib.strings.normalizePath "/mnt${lib.removeSuffix "/" name}"}" + )) + ]} + + # generate ssh hostkey + mkdir -p /mnt${lib.removeSuffix "ssh_host_ed25519_key" (lib.last systemConfig.sops.age.sshKeyPaths)} + ssh-keygen -t ed25519 -f /mnt${lib.last systemConfig.sops.age.sshKeyPaths} + cat /mnt${lib.last systemConfig.sops.age.sshKeyPaths}.pub + ''; + } +else + writeScriptBin "setup-disk-none" "echo 'No system config given!'"
diff --git a/packages/shaderbg/package.nix b/packages/shaderbg/package.nix @@ -0,0 +1,47 @@ +{ + lib, + fetchFromSourcehut, + stdenv, + meson, + cmake, + ninja, + pkg-config, + libGL, + wayland, + wayland-scanner, + ... +}: + +stdenv.mkDerivation (finalAttrs: { + pname = "shaderbg"; + version = "0.0.0-unstable-2023-03-16"; + rev = "027d4f87fd542c79d4276b521e39025477b6d03e"; + srcHash = "sha256-/HtbS+vn69oEDVP4HDBvnmpkGRLz62j8lCZx+plrUeI="; + + buildInputs = [ + meson + cmake + ninja + pkg-config + libGL + wayland + wayland-scanner + ]; + + src = fetchFromSourcehut { + owner = "~mstoeckl"; + repo = finalAttrs.pname; + hash = finalAttrs.srcHash; + inherit (finalAttrs) rev; + }; + + meta = { + description = "A live wallpaper program for Sway and other compositors with wlr-layer-shell support"; + homepage = "https://git.sr.ht/~mstoeckl/shaderbg"; + license = lib.licenses.gpl3; + maintainers = with lib.maintainers; [ zaphyra ]; + platforms = lib.platforms.linux; + mainProgram = "shaderbg"; + }; + +})
diff --git a/packages/vibepanel/package.nix b/packages/vibepanel/package.nix @@ -0,0 +1,52 @@ +{ + lib, + rustPlatform, + fetchFromGitHub, + pkg-config, + wrapGAppsHook4, + gtk4, + gtk4-layer-shell, + libpulseaudio, + udev, + dbus, + wayland, + ... +}: + +rustPlatform.buildRustPackage (finalAttrs: { + pname = "vibepanel"; + version = "0.14.1"; + rev = "v${finalAttrs.version}"; + srcHash = "sha256-QsYSfNzaAtUTJGuGxikproRgMmmcKO4ZCkyrbruh4Z4="; + cargoHash = "sha256-PPwMNvhJCvjoMwCBu0aeONXBNlCecgYUnwm9PnjHI0c="; + + src = fetchFromGitHub { + owner = "prankstr"; + repo = finalAttrs.pname; + hash = finalAttrs.srcHash; + inherit (finalAttrs) rev; + }; + + nativeBuildInputs = [ + pkg-config + wrapGAppsHook4 + ]; + + buildInputs = [ + gtk4 + gtk4-layer-shell + libpulseaudio + udev + dbus + wayland + ]; + + meta = { + description = "A GTK4 panel for Wayland with integrated notifications, OSD, and quick settings"; + homepage = "https://github.com/prankstr/vibepanel"; + license = lib.licenses.mit; + mainProgram = "vibepanel"; + platforms = lib.platforms.linux; + maintainers = with lib.maintainers; [ zaphyra ]; + }; +})
diff --git a/packages/wleave/package.nix b/packages/wleave/package.nix @@ -0,0 +1,77 @@ +{ + lib, + fetchFromGitHub, + rustPlatform, + installShellFiles, + pkg-config, + scdoc, + wrapGAppsHook4, + at-spi2-atk, + glib, + gtk4, + gtk4-layer-shell, + libadwaita, + libxml2, +}: +rustPlatform.buildRustPackage rec { + pname = "wleave"; + version = "0.6.2"; + + src = fetchFromGitHub { + owner = "AMNatty"; + repo = "wleave"; + rev = version; + hash = "sha256-+0EKnaxRaHRxRvhASuvfpUijEZJFimR4zSzOyC3FOkQ="; + }; + + cargoHash = "sha256-MRVWiQNzETFbWeKwYeoXSUY9gncRCsYdPEZhpOKcTvA="; + + nativeBuildInputs = [ + installShellFiles + pkg-config + scdoc + wrapGAppsHook4 + ]; + + buildInputs = [ + at-spi2-atk + glib + gtk4 + gtk4-layer-shell + libadwaita + libxml2 + ]; + + postPatch = '' + substituteInPlace layout.json \ + --replace-fail "/usr/share/wleave" "$out/share/${pname}" + + substituteInPlace src/config.rs \ + --replace-fail "/etc/wleave" "$out/etc/${pname}" + ''; + + postInstall = '' + install -Dm644 -t "$out/etc/wleave" {"style.css","layout.json"} + install -Dm644 -t "$out/share/wleave/icons" icons/* + + for f in man/*.scd; do + local page="man/$(basename "$f" .scd)" + scdoc < "$f" > "$page" + installManPage "$page" + done + + installShellCompletion --cmd wleave \ + --bash <(cat completions/wleave.bash) \ + --fish <(cat completions/wleave.fish) \ + --zsh <(cat completions/_wleave) + ''; + + meta = with lib; { + description = "Wayland-native logout script written in GTK4"; + homepage = "https://github.com/AMNatty/wleave"; + license = licenses.mit; + mainProgram = "wleave"; + maintainers = with lib.maintainers; [ zaphyra ]; + platforms = platforms.linux; + }; +}
diff --git a/packages/wlsbg/package.nix b/packages/wlsbg/package.nix @@ -0,0 +1,60 @@ +{ + lib, + fetchFromGitHub, + stdenv, + meson, + cmake, + ninja, + scdoc, + git, + pkg-config, + wayland, + wayland-protocols, + wayland-scanner, + libGL, + mpv, + fftwFloat, + ... +}: + +stdenv.mkDerivation (finalAttrs: { + pname = "wlsbg"; + version = "3.3.7"; + rev = "v${finalAttrs.version}"; + srcHash = "sha256-e0eOLsfU4wrz8Ck/iWwSnimTSYICmN/3vKeObmSE6zA="; + + nativeBuildInputs = [ + meson + ninja + pkg-config + cmake + scdoc + git + ]; + + buildInputs = [ + wayland + wayland-protocols + wayland-scanner + libGL + mpv + fftwFloat + ]; + + src = fetchFromGitHub { + owner = "Sublimeful"; + repo = finalAttrs.pname; + hash = finalAttrs.srcHash; + inherit (finalAttrs) rev; + }; + + meta = { + description = "Wallpaper tool with shader support for Wayland compositors"; + homepage = "https://github.com/Sublimeful/wlsbg"; + license = lib.licenses.mit; + maintainers = with lib.maintainers; [ zaphyra ]; + platforms = lib.platforms.linux; + mainProgram = "wlsbg"; + }; + +})
diff --git a/packages/wooz/package.nix b/packages/wooz/package.nix @@ -0,0 +1,47 @@ +{ + lib, + fetchFromGitHub, + stdenv, + meson, + ninja, + cmake, + pkg-config, + wayland, + wayland-protocols, + wayland-scanner, + ... +}: + +stdenv.mkDerivation (finalAttrs: { + pname = "wooz"; + version = "0.1.0"; + rev = "v${finalAttrs.version}"; + srcHash = "sha256-mOrxRjvvcPTauBwau5Za/e3kqyN5ZJtU9ZTqmDyj/YY="; + + buildInputs = [ + meson + ninja + cmake + pkg-config + wayland + wayland-protocols + wayland-scanner + ]; + + src = fetchFromGitHub { + owner = "negrel"; + repo = finalAttrs.pname; + hash = finalAttrs.srcHash; + inherit (finalAttrs) rev; + }; + + meta = { + description = "🔍 A zoom / magnifier utility for wayland compositors."; + homepage = "https://github.com/negrel/wooz"; + license = lib.licenses.mit; + maintainers = with lib.maintainers; [ zaphyra ]; + platforms = lib.platforms.linux; + mainProgram = "wooz"; + }; + +})
diff --git a/resources/default.nix b/resources/default.nix @@ -0,0 +1,21 @@ +{ + npins ? import ../npins, + lib ? import "${(import ../npins).nixpkgs}/lib", + ... +}: + +let + haumea = import npins.haumea { inherit lib; }; + +in +(haumea.load { + src = ./.; + transformer = + name: value: + ( + if name == [ ] then value else (if (builtins.hasAttr "default" value) then value.default else value) + ); + loader = [ + (haumea.matchers.always haumea.loaders.path) + ]; +})
diff --git a/resources/patches/mqttwebui-florapatches-owo.patch b/resources/patches/mqttwebui-florapatches-owo.patch @@ -0,0 +1,50 @@ +From 4cdecf84adac2671e1506d640d88c5070cbe23ad Mon Sep 17 00:00:00 2001 +From: "Katja Ramona Sophie Kwast (zaphyra)" <git@zaphyra.eu> +Date: Wed, 20 May 2026 11:33:54 +0200 +Subject: [PATCH] florapatches owo + +--- + src/webui.js | 13 +++---------- + 1 file changed, 3 insertions(+), 10 deletions(-) + +diff --git a/src/webui.js b/src/webui.js +index 3286982..76938c3 100644 +--- a/src/webui.js ++++ b/src/webui.js +@@ -334,7 +334,7 @@ const renderPage = (pageConfig) => { + <nav> + <header> + ${backButton} +- <span>${image} ${pageConfig.title}</span> ++ <span>${image} ${unsafeHTML(pageConfig.title)}</span> + <button class="icon connectionStatus" @click="${() => window.location.reload(true)}">${unsafeSVG(disconnectedSvg)}</button> + </header> + </nav> +@@ -383,11 +383,8 @@ window.addEventListener('DOMContentLoaded', async (event) => { + goToPage(); + + if (!config.disableAuth) { +- mqttOptions.username = localStorage.getItem('username') ?? prompt("username:"); +- mqttOptions.password = localStorage.getItem('password') ?? prompt("password:"); +- +- localStorage.setItem('username', mqttOptions.username); +- localStorage.setItem('password', mqttOptions.password); ++ mqttOptions.username = config.username; ++ mqttOptions.password = config.password; + } + + if (navigator.serviceWorker) { +@@ -427,10 +424,6 @@ window.addEventListener('DOMContentLoaded', async (event) => { + client.on('error', (error) => { + if (error.message === 'Connection refused: Not authorized') { + alert('Authentication failed!'); +- if (!config.disableAuth) { +- localStorage.setItem('username', prompt('username:') ?? localStorage.getItem('username')); +- localStorage.setItem('password', prompt('password:') ?? localStorage.getItem('password')); +- } + } + + location.reload() +-- +2.51.2 +
diff --git a/resources/shaders/background1.frag b/resources/shaders/background1.frag @@ -0,0 +1,78 @@ +/*** Uses noise / fbm apparatus from Clouds by iq on shadertoy ***/ + +float pi = 3.14159; + + +mat3 m = mat3( 0.0, 0.8, 0.60, + -0.80, 0.36, -0.48, + -0.60, -0.48, 0.64 ); + +float hash( float n ) +{ + return fract(sin(n)*43758.5453); +} + +float noise( in vec3 x ) +{ + vec3 p = floor(x); + vec3 f = fract(x); + + f = f*f*(3.0-2.0*f); + float n = p.x + p.y*57.0 + 113.0*p.z; + + float res = mix(mix(mix( hash(n+ 0.0), hash(n+ 1.0),f.x), + mix( hash(n+ 57.0), hash(n+ 58.0),f.x),f.y), + mix(mix( hash(n+113.0), hash(n+114.0),f.x), + mix( hash(n+170.0), hash(n+171.0),f.x),f.y),f.z); + return 1.0 - sqrt(res); +} + +float fbm( vec3 p ) +{ + float f; + f = 0.5000*noise( p ); p = m*p*2.02; + f += 0.2500*noise( p ); p = m*p*2.0130; + f += 0.1250*noise( p ); p = m*p*2.0370; + // enable smallest component for more wiggliness + // f += 0.0625*noise( p ); + // f /= 0.9375 + f /= 0.875; + return f; +} + +float nat(in vec2 q, in float z, in float mx) { + return mx * noise(vec3(q, z)) + (1.0 - mx) * fbm(vec3(q, z)); +} + +void mainImage( out vec4 fragColor, in vec2 fragCoord ) { + vec2 mouse = vec2(1.0 - iMouse.x / iResolution.x, 1.0 - iMouse.y / iResolution.y); + vec3 col = vec3 (0., 0., 0.); + + float s = 1.0 / ((1.0 + mouse.y) * 45.0); + float fbmz = 1.0 + 0.01 * iTime; + vec2 p = (fragCoord.xy * s); + + float as = (4.0 * (1.0 - mouse.y) + (3.0 * (1.0 - mouse.x))) + 7.5; + float pdir = nat(p, fbmz, mouse.x) * 2.0 * pi * as; + float d = as * 1.0 / length(iResolution); + vec2 dp = vec2(d*sin(pdir), d * cos(pdir)); + vec2 q = p + dp; + vec2 q2 = p - dp; + float qdir = nat(q, fbmz, mouse.x) * 2.0 * pi * as; + pdir = nat(q2, fbmz, mouse.x) * 2.0 * pi * as; + vec2 c = (q2 + q) / 2.0; + float pql = length(q - q2); + + float mdir = (pdir + qdir)/2.0; + float ddir = mod((qdir - pdir)/2.0, pi * 2.0); + float tdd = tan(ddir); + vec2 co = vec2(pql * sin(mdir) / tdd, pql * cos(mdir) / tdd); + float ro = length(q2 - c + co); + + float rf = .25 + (0.2 * (1.0 - mouse.x)); + if (ro < rf) { + col += normalize(vec3(cos(ro/ddir), 2.5 - sin((ro/rf) * pi/2.0), 0.9 - ro/rf/ddir)); + col *= (1.0 - (ro/rf)); + } + fragColor = vec4(col, 1.0); +}+ \ No newline at end of file
diff --git a/secrets/cautus.yaml b/secrets/cautus.yaml @@ -0,0 +1,67 @@ +acmeTSIGKey: ENC[AES256_GCM,data:YUFV6XU3Uw6homOoxy/gDYgnT/coLzA7n6POEainl/WaoiBN5u6T1HKTYo8r0df057e71G9noe2tH6IGBzO4aw==,iv:1BYfNw5U5aXu397eJ32zFpNnHiP5uayc2Py4SDcMmOk=,tag:xoAZbUYRHR/mBp3gzHvLWA==,type:str] +wgPrivateKey: ENC[AES256_GCM,data:cyEd54xKsPxKTebdtKDJKWlByGY8+30NrjsnvcoAjZbsYbymtdwM1mGE1wc=,iv:1jzfXezrW9WDZGujvtAL4KGT6Mk/W7sLfk6Dq+oUumY=,tag:XKBj35fHvtqbksjkXbhtNQ==,type:str] +knotKeys: ENC[AES256_GCM,data:emLfdkZnykMmwPNlWbohMmcqpePtgU6Nynj9Am+LZbVaRRM9WWDMEtWoMFdTiAQpVpethgcPbzHQocruB8ZfRAOqCG1dVuvlh+AfWKwK9VVxWMHDDAmCmxAGsLPUaeY1C54RzT2GzJyJNRFitFKzfg9NcgMsBwaXllg4TnSQeCT5ipecmDcyT73jjYZOfjPCphVJ0jn3ELRsLQlHYxmn/amaKWr25xpWHv33tu2uzyt4ZP2jfLAuc4HaTY5WhGLPjOUUPsi/TfU0K9iN+HSIeuezIF2NENRlpGSO44LZ63pW/aQo7OyCePFdhgFtqPiAASKDlA7UpygpcOHS7sq8S2eBYTIZ1psyMxADOtWrx3ku9YEcDWZtq1odipfGdpk566RUXJ1PyKHucXynGxQfTzRXv+aj5uhr+hSmyLhhBupUU55JS78UN+EzgP8VyGEDd/+HKCA/rfr7XHXZDD5xENIJp+DETvA7zoF+U1AKX97ZDRJ3VCK5pq27YBZ4r9qOqb8+LPadHJ/BVZU2yMvH8AZh+0We5GQ7iFS/C4c77f+Rng27dBuheAEkabQjiSZTnO1s+bRU+nhgxvsXlvDjpHfeZRnq56hjfnNzd9OKki9V4pY1SorOXIWtD42X9NwizbX22zcowKof1li0AKLBrP9oGfZO7vEQxCx/Kp8Ln1Zugh3hvWYgZp7Bcg4We6gzRc38alUyvqsoKf/iXA+eGACcideDGtXyi/Moreupwnr/4mhiARro80OhlE0wEqrJj694wC4dcRC4rtPU8RS09sa+GGSW8t25WiS3Y9xLHyZ+fjE5CNnHh1FZzSelWYTDxHf9kdFftLc6yNjNZjwnwLjbTp7dlT3bFuZCwEeb6ctiie8nvh61KID08B1IrOy3+55TvJqWjzpi+5ARPBFvCMnzpnkEsOPSc8e7HYMTHrGTfVBfhYyEC0uY6yq1YdYUcxaBLrF6zzpB2OD+XA27FuL8YSJHOpDj5jF+Zc4iwptY8ybGnDLBT/NDrzWSk1oxq/u79Q==,iv:zag9eUOJFjI7tZl2HimsEa9V7649xUOI2w/Oh2lNfZY=,tag:ipGKyJwgUgzBXMJSbR4vYA==,type:str] +radicaleUsers: ENC[AES256_GCM,data:HbF45OTsp6AQCKi5DkOkU66y47unaE9EqW1MdbZPb9I5Jk4hk/V3ARIBdXFM4o5FUDgcwVs2sDtDRMCynI5LTLwXfOBxFpYPzziOTN1LX78A1v3nuzgbcJNT5Ycl5sx5+NVgaCwxE/H1mgvFkLHNwiXof5jI4V6mT1dWdKeQDLKeJjmpanv7+x4//rD3JxfS/mS/zYphN4ewbIc5DDHgfK9cW/EqFneQwhXV5lsPyOiQTr0Vq/c3GYBcW6HTpWQYhHy+hagvOcAwQo3bosk=,iv:FSKQz1ZQHKOoEX0NnhHwez8RBZri2Wo00ii7Ps8o3GY=,tag:XGMlfhH4e46oZGg9qM4oPA==,type:str] +mailPasswords: + katja@zaphyra.eu: ENC[AES256_GCM,data:Z73DRPcdrk/XINsZ7w+F2RuXIZ6NvBKtzeWk93pwSAvJu5SOkCFKEYpp6wM+PRYOHXRyW0nk6X3gb3ux,iv:gGiZW4eZhVsiun7wW/d1g6T+gKGVWGW1GG1g6hkt1WA=,tag:eMzsqXwesLDAEGo21FC6Eg==,type:str] + gts@zaphyra.eu: ENC[AES256_GCM,data:y7aHceJr8+joFhQSuTlU9KtfH3Xw6VooSpnuSve5TLP0UPXW5Z9NN7lALXfHd8b42JTuEL54XBWP+ryd,iv:3S78h/QH8Qve8zJBdQqmgALatLISEx7p9Qm+t3mSL4U=,tag:1mDc5pvVc90I/v7SPowynA==,type:str] + vaultwarden@zaphyra.eu: ENC[AES256_GCM,data:skwSweCy4NgK/r+4qx0JsVXdPm8ZvA1Gu+wluusxlUZOMs+YGLKj83fxt3ZwZ9owqPMV9zsSiDfgS0ax,iv:DRqdmk4ZQibGCwsc/0h4F/x+RPqRxExGiT+ll+YopsE=,tag:wk7daIn5W1iSYLhmXV7avg==,type:str] +environments: + vaultwarden: ENC[AES256_GCM,data:SmSotLiEvFuAKaddbsGlkEDMJ+ru+QH+7L3er/DBS+IvUyUMXd9BcULkXN6nMw7eW258S3MaUQq+AMOVTg+ofD5IMOs1KiH9sYoWU/Z1nxTJ40MDK0kGDrL+fZQduoAsAf4RgQGAIT+kqINzn28/kxmMJtgdCbQvaCsFKt4gDtVb+ztUmFhI6jdw/X18bx1AK9zvOJ9qt1XbV/m8HEclsNzWj6ZmZ2s2G1A/cYTQBJsJd9tNxBim5Mgi6G3AZAsGBFkd4wIuLl4dbIfo9jq6DKxcLT9Jmm5YMupXbdsarvHoKrpxHhL741TcQkNNncbuSKo21rgLZQkqB8ont8Jib1+G5cgnZq6Wz2plZMD+H9KKsdKV,iv:ELSX/EAHwsvLcqSy/pLAqFKJO+EypIBGNNVBthtNOZw=,tag:hESMXdqXMhCkEfHEUxB1CQ==,type:str] + gotosocial: ENC[AES256_GCM,data:x4uEb0wOvvF166TB6Q91m+4mDrOO2fXAuWj1FlInOfmEY1Z7Jdk/JxELUhifAm3BkOZi2Hw=,iv:3okHmE6kAsCoFWNcXwpLgbFvRz9ApV+cUcb0sDzHSyY=,tag:UNKEABN3UQKnRJMwM/GbDw==,type:str] + mautrixBridges: + signal: ENC[AES256_GCM,data:j1RtojUJauGsMVUbJuMqEL8QFRxiF93OAszAPHqtF9+KFlo1FrEsuKyNXDqVNAgjcl0MiAvyI8kkJ1d/VD+Z5H6vXoPwO2Ykj8aDJskGacBxgWgXSJohDiIonNKuacVugBoihGjsB4C1MdNLd4rxImxWTeq6o9mVVboEuyYMv5slv56hqD0NzWv/Bl0vpNYjoOecsle4gFkk2r8bg/dpmm8KQjwPCOAcREswf5wsZHkHrCLCL+j5a483qQQ/gi0IR2HTLyORXA+8DCX9yDyLkNPfbL1YXs8MXi+NzFjBf52E3v3F54/kxSg4uuThrFPuEW3Zi8+sJaaXR+/mCek0NPR278prXdUZcPiayge/wrr7rzqghv48SKQ/z+rIsaNy9krCgRNDvHHR2my3o48fNVZy/QdjqsKwtX0obiTmkjCzQluJrP8V//KH/FWjNijK+d4lnTxzsBLYTmvC/uPL0e7fEPNNscikbr4L4K2kXtbWhxFNiqPKRuk+315hFxsw+0IOEvLocqdg2sQCTfx53ujvKPqviICgKJ1ddLCm7rtV,iv:MsFvmu/YT6AukMbLmQBYM9bB3CGYoxOcUAMeVqPtf3Q=,tag:kwWg5CZej48GJ+UaqJLvKQ==,type:str] + whatsapp: ENC[AES256_GCM,data:RjlY5duOUwzm0qBZRfQ6yas6NNPGUVwKqj+27rYGhz9xRSmK6KDAYysmaNttUgM/V1bXn8yAs3wxKMnyaC/Nr4kdx5oNqSBCsKAtWpQipJkWPE229VnQ1GJc9plmHaRd5NONp9bV1S2npzuMUxG53rcA9nzdjygHVeYkeaOH5xmoiIikJU7pZV8hfZ25G2FA7xU3IyVtUrqEUr1jSJhyXq/FGskDRd2T5RaMTGN/ymOTWtaOD6almBlHIz7Nz9e38+OkdkHK8pj9OCwtcENzUWSNrmlztlLGHYpDOOtGJrjQjVCYyb121v0sP7sWMVpuB7N4cWp0RENgkP/oZvPG0Wgnwc25uJW7nJAjIMQulvetuVqeDsrDH1upjGZMzXhc+VSFdCb0h8mck3w2JDHSwRUntbjKbwqY9qUGu2f2MVw3LQxoAoTZiEdx22AwO4oGHM4BWTQzEqq3i4PgeWg9LjRMNRYTN3M0g0FzKFg10oleN7Byfxifm9OqeRR/pq61DpyyhVjg5DBCBlFa/6I5rde/uzMGTImF04TxdwEPjmwO,iv:jF2hvwVdyevUU6jr4j73FKP1unSk+Q3BT3mHg7XG5ws=,tag:5emCyANfl+q8Ei2qspgdDA==,type:str] + telegram: ENC[AES256_GCM,data:KheGPAOJYfaaD0dGg/R1aJQHVE6GesmiCTWTN+aZvOBmkAm2cusYLguOFGfmKFa8I8yOBxaTu2/7HYAabJOHORlSw7XbTna67aWRmo3pskLz/3Bb3ugTpEtpNeCL90ZdR9IVSIihW1VTVp7UKZ35yVuNSV5Jfs1uuFGsiPKp5cJ8Kftz3bhDOgYPCyqojqkqjuXoELn/sL1NnxV0Ctj2TFgupQlk/u/gAkT05Zj0YEU97JzLyjAOypOR2J5ZtcEDZkcrOPr/Z9cfh52lVL6JaIkZAoDLpxqp8RL9XvKFS0pnzDhAZIqe1hrx+5BLRpjx2wPcLPSlN3yUyqTnv/F+QXJQi21VGWaCFl+laHuepLaDVVoBwVA5bEjynU1/aFJbYQsA9/jaI4Xo1s/VzvcRsEtXNApC2Nz5TYXeOvnTJIKKz6fhBB1ULpLklXK+9m16QlajH3B1F0ndhXz9O4vOFUXF4IZWBbwlDVAiQzcgVge7hoem00gzkLXeLGkGBq1GysX+Kpz462/YRT3Qa+MhstSVcJSdPNYM7HM4QPHfViaIZHEY0ArTAETJad8hZRs/OXyq5mxcD2KMBrVlOQHYENr015xi4zaXYQlr2lcG+fQX7vBfw8EmnyImI35ZLbobS1u6pIhE4yUE2HMsERBy,iv:6fCTzfCnpjNcGNv/9b1KwIoGxv958mU5Xdd4ZOv8ifc=,tag:OytLQfY1RXz0zwlz+Vd1QA==,type:str] +restic: + memos: + repositoryPassword: ENC[AES256_GCM,data:5a8lHWztE/QJoSu+EDT7tTVvuG2FoDJZYspKatPSsNiIlDXaYs7Hn6Kd3v9Tmpnn/A4FoKNlUvkhdqJmy+KAhQ==,iv:Vuxzv37simZtd48yrO32f8Kk4ddEzeS3yLzfmd0cjTw=,tag:b5XNowySIt4vhokuBQG3kw==,type:str] + sshPrivateKey: ENC[AES256_GCM,data:K+ikDKGEOua1kO5iDS18WMSxGwgH3yW+VxyEJLnEivlUZdVVIsD6zGPqYwixaqgMjRifTFMVLzng0gOWlXMJJF8PdWHV+W0IMPXW99DQCwz6quMmGv3p9wjOOWDEiNpsaA3AhsHwgWE2OZnWiB20QEJiP6+cyvmoe8rft8oJp1/OTxF5t9zyWr0IIpgzV+CocDI90yk7nzCzPcSFEc5RS9H8Z3YbfxAOmhXn3I4y1D8PA1EbOjjx6ZhuhqvWdHcQZg8aGkTl+v5KQOGTF0UvMvrq9jar7XsRKmAz0mdT7JRA30/o6QDT13kOHNTW0FbTyVsmApmMHimxycEvPsNHXr+iaD8ZPArEfhAc+j4DWaFinLpwvR+BGeNZkAxQ0OvE0HhVKI9n0J6uCoDu9CEPBOCE5PEZsA3hyuqhgA09clagoIm353JEVinhRX4/mHFo534JgW8W3X9hFPGqBnWyFpOw61Xwk3hJ6JIYfkXo+qooolRXD2dFgpgPh9Kc7k5KSlPJ,iv:VSJdUlylc0riTl7KKzfFZUj0UQUG8FBfvXFTHSQEYvE=,tag:glVT0wlbF2FBGXl3AKUb/Q==,type:str] + continuwuity: + repositoryPassword: ENC[AES256_GCM,data:CEI/UhOidO2woYhdmhaTALU9vzya09Y6NH8ay7/+VSRTdTOkKeoA6nvSZ4+BApGrRh7wmZ1/o2A657JAgQKaiQ==,iv:vzlp+q+qT/lFVmGq6h3HaXlk6GneWCPKQ7kMfOLFlNg=,tag:hLsc/7Mv5H43lsSXL3wFlQ==,type:str] + sshPrivateKey: ENC[AES256_GCM,data:gf+HMn8+uuhqP1yrTtFzQ7bcWiPk1scIILRkdk1q7QJt7z8U9L/MwK1gvTkahH8gBG+2cbsyX4Ya1sY+AZud647+/TsYnkkvH5pDFp+DPfg9MkgmWhUszG1ZywMwuJWmUg3PbDz8dX76gQFRllboYTYHmY8QJf++bhyEdixzghRHBl7XOxdbWWOdDrNvY8Lqz3mCwbfVAbfqkkKwaQISxySv5zj0WVEbT/L59tTCxeRL6a7S3GB1szBKivcxUZZlVovm4Lx8NNP0ZT+fHBGOccUe4FVh9AqvVua4RQxy+fJ+js7aJXmQg6I0cZpR1iYPxDBskZkocNznF/qFBBjloAuf0JenGhu7Es1X0TMRL/35ZQy4jmtNLQWlLQDjlnA9mxLeH5nhAC7Qte5V/Tw907RZb1+tGAj0haiC/xn5Lqz8KUq1Dc/7DgUidvbhQ8w1f47rp8ImaoKvR2yFbvXnuJFVWZCpNpLt3TgJ1Nz1CmWgTIIzpj9j4AP7NF5PNblqOlyU,iv:/F2NicUt02A9TQBavthp0sskvGJFND+TmoDX78FPvtg=,tag:OW/uBHgcaFeVAXKBiKykmg==,type:str] + radicale: + repositoryPassword: ENC[AES256_GCM,data:aBfXkH7/BzFANMaz3pRrc8ei8k2sVBLRSZcBXpQg/5sbGtC8nXSlo3LPuhxeUD1mO3Ez37n4GnCsaPIMZf5guA==,iv:/5sFfRT4sCwebeP6BLbHhecuXAZfa+snQH+a8KAI43o=,tag:ASoEUEONm+k8byGv+UK3og==,type:str] + sshPrivateKey: ENC[AES256_GCM,data:SRjmTTQ406g6dMKWIVRKg4fpR8axgIJJc0Fft4OhKMNtA/XoQUK/ucRr+wG1qRWP3Vj7M8aEAPlQcEk8LUSxLf0O+MHM+5Dd78tjFdxsFVH5MuMVIFTyPToGXELJZmjnRjzbrknt2ZKVgEACyGB9zRo1kd8m1eV7Fx5oE8t02KQQjAQ3tbF7eUSD9Qq/uomOqpXi8wF0tIagnx/RLyUVsAydW3YN5A/5/XPFApwbTcUQmZLhgBJjKwvArx1wT1X7PWSJ+cDOJ9ERX0xtjpUeJ5B9J2uCFcWcP+Biz7v0xZlHX/T/+Do5k8UTVcv4pT7Tan5bM9h6QCLSeqsmuAe+KjXNHUVyoMELK0C6PSz41jUo/1WxhP6XbApICNli9LUIKA/u1F82hnYAEsfAu09xReQAjq8/sGCX3mwnt8OL2DL1+vcsjyTL4jOIA9ljc5ajphtl8bFDJTzSvMZbJkHaNzpXrAzl5vNqzGQPhpyVKA7Iz4cwGa45EmhEo2zEXA5fGIkI22MWNqeIbAwcaLy+,iv:GhauOD22NaQGZ0P5iH/wFX2StDcUxTiJnuXe4S90FRY=,tag:1WoTcwHP+6c85XtGWFzlVg==,type:str] + prosody: + repositoryPassword: ENC[AES256_GCM,data:KH8+bvVpCJ5DovQcDzkZNFg4tivPqk0cqrSxyAwp/Hef7KGST3PEbUpKOVDlM+cWXud0HnmEUDW+scIjcBqQRQ==,iv:a0LcEn5k58IvOpX5tvbhdVMDR0USL9s0jLRucQ/r9OY=,tag:iKc4MkCJmV7H8+2Uz87EAw==,type:str] + sshPrivateKey: ENC[AES256_GCM,data:aOEhQJ+zrMWHPxiqPuksjR1R0/uM+rdl7JB+lgpH/3W/6SMAKC0yqMOgcThqTnARsqDiC6+bsH1ZaxDctlcrE5okt8nzLz3XuE7NrF2oZOughsAUF29cYtlAjYSQdGYDt0dxSCvozDn3rpnilaOBHpCOKRSrHbt/s8E7bJ49eEmyyJ/axUAnl3l1keACacHlLK8K+pQUZS3/yxHwFfhzLw/r6X+ykrO2tI8Cg1hhwJgq71Jy9LGCgY+k2tGNl6UH0wKqJL/M5SSDFNUA9Tfz+VXyDdAhal0sHeQbfQrYO0nu5CrNlMSfyMho6AifAqeX+KPQ4TSRN1qhX2zJaNdvMh496+iY75sbxo+m3jLLa88hbKEgaDm74svbJtpafgkak9Y+qyogJXQFvEcWaKBxXkeilPrxkE2b5meqjfG9mXKS2bIgl/u6+2JL0gJxJAgF1NfqSXUG6klYJxNQ4Hh9dsdbcRW6yIgyliBW0WVVcxoo/nxa8spwi0WyaGJdWKwZJxLOvA5Te8ArR3FecV4i,iv:XokatEWgo6SaimnLLKZkBZrBhGs5B6bkXnRGtkbKW9Y=,tag:B8J6QCXkUpcNKJORSCzr+w==,type:str] + vaultwarden: + repositoryPassword: ENC[AES256_GCM,data:YwPxvt5svhhfc6CQo7XJiFUEqRnBZABWEDBkGnT3MDvkZFIG5DlGUh6MbKYSmfmbYDulek9BNtvuwOn5FkbjuA==,iv:ftServQF3pQwDD3LsYggophCQH9f8xQsuVuLVbgjDTk=,tag:RiMeDvxdQHaL3Vr6FSxBgw==,type:str] + sshPrivateKey: ENC[AES256_GCM,data:KriiyX5+HotPEvhDKS0Eyfla0BCOdZZGurk79Y6tuVum0E1RD4UvX1oqSoUGdIO7UYGoz1hkMwKyRyrx/FmirqQXHwWYCThkuufGTcS7vCFB/GlEIuZ6rnqeKlFREZ5wnUccUITS9U2L/hX9IoaOixo3mlIIXTQE96qNWqwlT4MJe0IPQWEwE8fesMJd1kD77CfYvf1yPDplZFawNqIZAWrENmc47M93s9GRbr3oZs0Md+JnYOjEphqFLHaa4QkZq3Au2YvYjV665Ln55mDtXR0i/Rw+mRXHSXd4CIC7SNuSQAnV9iFb8dYRLwtsVCf8m/aSDgi6b/eCz5JjS1oBKCwq2fjfNRAhcnWkUFa4f91gPcteDj5t/QNlIDYwAtkEODtBylZ8mQerOv8dAOjYCuHgpSDAS1Wo8t2AxFcilVOOwMwG6T8vsJdZZcYmvdZV/vniIOjnv2dE8ZObrdRkYS6eQZMhfTUtLPOB8G/cDzm5wmqmrsrt29pJ5/DEjPORG+pDTlh5lqihMCdExJzE,iv:PpdROtF8Dr9aChJ7srXRI1iaeMMJilis/d+ksbTfcmk=,tag:5FQgU1nvoXwekHtUZwYWfA==,type:str] + gitolite: + repositoryPassword: ENC[AES256_GCM,data:2+tOHmVa6QsdWsj2+zSaBvav+aLlFooWOpO6Yor0/t2v9c+/RJ9l42RtTB3beMNyVjvEHi8fk8yhhuNk9M8QTA==,iv:wuX9+V7EewRyPI5pQE3fC22rlP2zNzTloeuv/G6CpBs=,tag:6GDTzSfPY12vvb1JALF34w==,type:str] + sshPrivateKey: ENC[AES256_GCM,data:+kOIrvezJXYhNdiv3szxi+m5QIkB2wUpRO8Q4JYNjV+6OQsVWwLzSGbv1QGcByczIHFzRMwxUBBCheJERzpcpOL9wtfa+GnmXmdpsvqF4TQEy3SlHNcekLKua2p03ScuyOHJPIZK3HKtrNoWwRicqjjYkNni7aHgFfamklhpWkIF5D94MBn8p/nTiJs5hNGeOWbrvu2Mu0wVkada7PBRNS5lgGE2eb5qVmFFDteZQU4AzRDLdhDTCtOyv/W20Fg/8Mh9aTVeyUXcqqbGTml6xpy66Er8WsgoAmKBj35pYDhcnjoZicoDXtqcsk13swHtU4ybbqKQ72/i4BO89+TkMALmJw33wCRDD2kD9DUM/69+osfgdBQl6RArEq6X2AySVa8RguMA9lawV+vrhcm+UOH0/z/urWZ2P46iJocF688lHoBr0D+aeMIkodr3MbjJE6CSTMf4R/wEJdKqhRn4zNOY/ogRd7EEbF8XfT8+aDc09F43H2rehJBknrkq7d/4mh0Dey5FnA6L9gtfqlmJ,iv:OeVbjl4O8aLSy4gkhabcWAWjHpt2HmTQw/VnTs9DZqg=,tag:wIYFrCkr2lnLscP793Yxmw==,type:str] + gotosocial: + repositoryPassword: ENC[AES256_GCM,data:koDQEnK6L266YQJ8aqJlgosljmO8/QN3Bn5HyGDcB59BLr2qCsqMmIsBA4eMihKlb9FH3sn4zTJrtC7ncZiTAA==,iv:bPYv1AYjj2t+g8NSiuSmJfBI6e6L2smbxykEQZtnIOI=,tag:3rdAniOVvWUqCD7+Lrq/ww==,type:str] + sshPrivateKey: ENC[AES256_GCM,data:5GxdmhaST2D1CBzdcr6Xyvi/BJDTDiTAN2s71ErHg4WPNzGjr3kzunQ9RN/V+lS6m/KNYsOIrWtxhgQh4gj8u3UmP4fyU52PJ+Uu30zNFRTHsMhaZgOqAYIFD64a3sNZu/iM1KDHsTgnGkuT134ehYzkemEe++77ECqetep8teC2q087HT2Q8GzcdU+VL/Crqgk68cr7qiaDBA1IxPu0cTuVBcRKZPdK45bapYIl+ms/0aGMAoVjozfF+Za1DCsu8Rnyjo+DrTijQBFZapLQg2ApjTvGXTWcN9gWpxH26ebL9AmKjn2m4sdMFRGM6bsDv82RlHfI/P3FFx9ma2lDrgN75XterYMTAvsjg32KBYMrQa46Zn6mUnmxtwLiTCl26jmjq4V3Xm7WlSntwHi1qakmXSP4028YtYNYO+lUfozG9fHNpD7lEzjXBFNeGUmRKgCByd26e9ug9HkATEJhAnxYf3m/haQh2t3txmjriqQSHC/l9e8Ob87ZlXpbzOCq0kIqMsRv8dNVxWMWSAo=,iv:M8Tzhet/9ofccRJGHPLnSjY6ZSCUhBoFzGXsgH/K6rk=,tag:2nN4WvlTjW1unrmG1Ql1zg==,type:str] + mailserver: + repositoryPassword: ENC[AES256_GCM,data:zFLJ2zPha+EbnWz5cBMFqQH1rTZq/kwAA7XGK+c4s6ubdPfB8NTmRtvN1giSK6IwnkDBeeF/yi3CiQXlCFXnwg==,iv:sTXfDaPk+1S0IF5VG3oSdfYmtikvNggqF0z4zvFWV1s=,tag:W5QRnjfkxZLWOAiBl5LZKA==,type:str] + sshPrivateKey: ENC[AES256_GCM,data:oEVwgsHWiQqHPwAZwyTE/sgUaRxLye6SHL4jwQSsEO9CKl0ulEi1A4D1gmZhTDsPryysplKWT5Vo20LIO5UTqJ/fY9vLPkqKsAlNLQ7P/AFqRnyvbcvdwOyTczBgf8hdSoqw4OxIDHwx1gZNJ5QeGORz+iN/rzXxA8NmXxnCqcUvHVsx+P+sDdiWau28o8xZ+RrFnwfaVkQ+HW1USebweau/blNvTND9CnTS64f5KIvcpzrxq/iLrzcoI+OoXF/L38FVzN8FidXTQRsQZ7nSkjA/jafUIg/dGJMbjuBPFKoj5Tb2SdMLZbFgrfNX/URNj+fM2V6zmjHhD5UwUZZVRuK+20uFFfBuc/5RYq+h4Wlr/7GAKujxc45lD41Dl1E6yiKEXf+Np2xlWCIx8vMgKPGvSWx/m+BxueIdDsMO9HvYbIX/SV2r1y+9egg7r/zgvzPTmufJJu/pjUo1fKaFeJ26p4YZNf5H0gRRsgNeJQ2bdVyVc6DNxbmcJcWqBLmhqtgnLJ2MYsKeqfcAgpo=,iv:wNDzZzUxo7RKOeuD8QQ2qPFDVXGOmJ1jTOBJscEPayI=,tag:6P6D0Z8pQdKa6rB1zCTkJg==,type:str] +sops: + age: + - recipient: age1yln5qratl82u6sqakh9gn2c0s88nzm62cw9muq5z4kzgp9wqpyzshhptq9 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2ZGZuanFjekpVQ2s4bUNP + aXVyVmJGRXdxUzBPUGVoWE4valNYZFEwbjBrCmJKczI2TkVhb0VCclRSYTR0TGFP + QlIzbXF1NWdjaHRxbkxQdnVNaDZ0Y0UKLS0tIDBnUnB6Smc5eVFTbUVrVGQrcGcv + dDRUNkxscGdMa09JV3BSam42VkUxTEkKe6TtN8smT8dQ6Rg0JqNDXAeVXfooaVBv + qlBEsTO7KAJNvHL4cGUob/a3oKhOqHNMc8Tx65y4+1Pnl4oE4qQn5g== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-05-05T13:43:05Z" + mac: ENC[AES256_GCM,data:uEKoFAdps4vMsxUPWyvisC4EQxdS6FDvai95PyGvrcFzkjMOP6VA6jZ4klrwwMT3AEY3kTyeQA1h5sCZsvMeDfE1Jtd8deR9/59iR8EwX+ro98NqQyPW6BgLdQQKY/hpXqQMuUj33IZ7LOD5/1vUCSKp8557drMXTHLeQ4SscYE=,iv:cdJBn+mVhilA7c7xoqEItdmyyRmIhpvph9fWpoZfNA0=,tag:4mw6TdYW9LmW+wH+vaggoA==,type:str] + pgp: + - created_at: "2026-02-21T16:41:10Z" + enc: |- + -----BEGIN PGP MESSAGE----- + + hF4DfdBfTP3jZzQSAQdAOt/SXhsmCct4DMVVeHSdh3dyCuolIb2x8G/vG4/Wckow + QTURCDjjqHZlf69zlKtzF3QLr+MqQ1GCLR0i5QyDOg8s3jafYac9s0BNkO1Ia3+x + 0l4BcCGXxF5kdS7hlSasezrjEGRlgyMir+8wLyIIu+rZh+7tgxyH4A6EGYErObQy + Idt9vyI900uktntFLCu0RuXyChII1RG8aydWKADgFs369StEL22u3bDs9xkbHL+k + =w2fZ + -----END PGP MESSAGE----- + fp: BFE6386C8D66BCD4DAE14FC895F0FE7CD7E6A022 + unencrypted_suffix: _unencrypted + version: 3.12.1
diff --git a/secrets/clevai.yaml b/secrets/clevai.yaml @@ -0,0 +1,29 @@ +wgPrivateKey: ENC[AES256_GCM,data:tiT8sQEMH64xdfEZUuX/2n5dnnCsfh+905CbDkkvXhmv4wKS7I2fEKHP3bQ=,iv:x/gCId+VCn/EGvlOv4GWMq3y1UbtBZexgTDec/7Ii3c=,tag:+BSfqAiDTluAcXmhmSbVBg==,type:str] +acmeTSIGKey: ENC[AES256_GCM,data:TtWvxIQrs5S9Xy9tWVBstwFgtMoqYX4CU134dxJ8dS7HH/qDNPlPZu052rjTerCSQ5dnUaTKx5wFRxFkZd7y+g==,iv:l8GaGgg2flBW+toGLfgCt+JeemS3I5v9eFCOJpfRYvI=,tag:GLLwm/GABJ5u8Ni56MPF1A==,type:str] +sops: + age: + - recipient: age1q58tmhk5hxrfn86qymet3mznafdzuhwl47s8mt3nyn43pararc5sng7kag + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXbXAyWm5PV0ErK1RjYTdV + WXU4YWMrZWNXRjZDNmNNbkRFSU9TTFJRSGtrCjdSSDlrZ3Z2Mkx0T0tYdTFUSDZ1 + V25RS1R5MmVZbk9RRk03RmxocXZTR2cKLS0tIFhvenZ0cjlGYkw1K28rOFE3a29k + a05oalNCV0IzNWd4NHJPelVCODhrajgKomqxILIfKYLgwuNQhBGX5bxb4Iqvw6B8 + yiqMxHyPCxPJra1epCqVWlPEhf4VgUnrDCtn3YEyrZmWMm5tWY9Pxw== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-03-04T21:21:00Z" + mac: ENC[AES256_GCM,data:ettbV3coxarLZQK/mOY5RVvIifmjS+PgQrkuT8CggEoajmUVi96q1awfMSC+zThPAOdj/Q9XjxTGpquiScJj46rr6dWLCjzIfYdBVbaBgYoyQXHMYt0/4NGOgcCj0wQVYjmAQ5ZEf5kYvhlvlx0dZFsZUYorxUWJPn3Z6POY59g=,iv:AQl1lj2jWr7R6aVKgo8cQyNxU2vIr7qNNoSxpRN9I88=,tag:8q1E8GbgepFjUCLN2WpBiA==,type:str] + pgp: + - created_at: "2026-03-04T22:03:42Z" + enc: |- + -----BEGIN PGP MESSAGE----- + + hF4DfdBfTP3jZzQSAQdATuw+gnT2NdgMxMJxBYJvLVVtGAtk5xNkXLDFGDJ8Qyow + yIV9Y6id+EwJNI4uQok9LznDJ3TVjVKOaXkGZxb9sn2oaZsQGX/oUUOWcPVVi2Io + 0l4B1rIZry6jlJeT0u6Bjf4vqYffwXrmygv9d5fN12JHOFVL220pJQV3mxvrTrpp + ty1IaqzO63Qgqbs7SpCkVEVKg1VDnKuvFJ463EzOJ7XLA2TPwnGwOk9Ixy8k6I4T + =U6G8 + -----END PGP MESSAGE----- + fp: BFE6386C8D66BCD4DAE14FC895F0FE7CD7E6A022 + unencrypted_suffix: _unencrypted + version: 3.11.0
diff --git a/secrets/common.yaml b/secrets/common.yaml @@ -0,0 +1,80 @@ +zaphyraPassword: ENC[AES256_GCM,data:PejTikjFR8ilcVEJS9JI5Fh2I5d6qcxgxNg6BmgoDRhBqkEllF8LG96nnh8gpAlrumy2ntsU+q/sVrzE4Q/hpoYVRKIRt4i7Qw==,iv:zc5XnVy9fTfFNdnfiFwM60mz7RDh/3Y3iB22AzwHrZQ=,tag:KrNlWECBrUZ2A731vXCsCg==,type:str] +environments: + networkManagerProfiles: + voidPhone: ENC[AES256_GCM,data:7C9QJVQRSrw+P2jCJit3oT+gPxHW9pHx0XFj4uXuAOdR7Xk1cBzOJNiEGaXagycecw9OE+6A+pMuWw==,iv:UHNcKDeVPM3/K2cLEUa7X96dZ+10vTAjXDyNlDRH+8A=,tag:vMyw8f3bYogrgnewZj07ew==,type:str] + voidHome: ENC[AES256_GCM,data:kxQ7Bn0dULnFQnSR5PozRUK4GUhw3y9ienTyFNwcW1rlZ0d4UpkRRvpG2XehMrEfRaxds0RVboQ8Li3z9Q==,iv:gbB6R0X8Xr+e9/dvSAXuQIIHX3mF9j3H63A+AXXq0hY=,tag:kY/Gs5VSWtwBH2VjDDNYwg==,type:str] + zaphyraPhone: ENC[AES256_GCM,data:WxGXvbz20SImLfNTUOpBcWtDq9Ia6bqEQFqkijpNYxgyiv1cloZAcy4ag7n4HOE1vVxIBDNlvCtAzHTWAnzJzw==,iv:pA1HYcOD58ivIbRp/aoiuG6ZK5ZSbxm9F8d0tCIQf/A=,tag:mqpYSMiM55HErXItBrQ59Q==,type:str] + zaphyraHome: ENC[AES256_GCM,data:xMwLPSh78GIuiVh+4z84QgVQKOU7QW7VGjPJZpBwdZhvgXVQt5cMOmN4DD/Yev+Bme/2C7SigEY8bFJcgpHnbZ6Fp1E=,iv:fJtciKFZ2J3j32xF2L3XHS0cVBhPlOmpUzP4q0A3WHg=,tag:wd2XA4Q5p9TYjp9kfWDjAQ==,type:str] + grogHome: ENC[AES256_GCM,data:ASt5/wqH9c1yq6Ig6PAFEHwPEKCsigIDc+svk+H00jwdaFHqtivk2CzZKPfGe5U/keAJIQ0YsEFkuh/637LUCTz0U31MP7glshB+Fc5SBpexfY06r8IeQNyFNGMX,iv:hThh6hJ43LUfzZfprMgu+AtE98YdKuzj2/9nCmGkQJ0=,tag:aomJeVLG0Z6WaYKqy/DOTg==,type:str] +sops: + age: + - recipient: age1yln5qratl82u6sqakh9gn2c0s88nzm62cw9muq5z4kzgp9wqpyzshhptq9 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGZnF2ZzBWbXlpMzR2REtG + QUVYU08zYXBSTHIxdUNUWlRkU0hacmx2NldvCjRzNHI1ckhFZXdraWJwdG5idlhk + VkJQYkp5T2ppQkYwRnEyckE2YWJYWUUKLS0tIGs2VHJRYUh3RVhoVzlyVWRPUVp5 + anFvYW5rZTVBNFZXcU1oWDNHdHlaT1EKmvZCautaRbOXVVZjOFkigCyYrJUeg7DF + AdR33NoDm9UnmJ86eq2NdLGLFY1x5GU2CZbER+Qhwf2+05NuhtCdJw== + -----END AGE ENCRYPTED FILE----- + - recipient: age102glvtt2nwx4ymgjh85449exlhmzk56cayy9hkkpl82u6wm64qzqjar7ju + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqR25WcUNXa0NJTmRxNy9u + RWJINFpGQ3lWOWdBRzZqWDdhVGZmVnFvSWtzCnZWcGdRMHFqM3MzM1h2RGM0ejg0 + QlUrNVN3TFBDRDdSdjI5V3VQYnpCZVkKLS0tIC9ETkpKN1AwOE9Ma2pVMkFtOVND + RzdTaEw3WWt0ODBjWWFsQUtYNlV0Vm8KC86xa3uDY/BXgLKbqKQ62WwKPpd2hfbG + cdYy7G9q7immIaL5brTNwmJG2tU6oUw2mkUF6DICv1fCJkTz+kTDQg== + -----END AGE ENCRYPTED FILE----- + - recipient: age10ujnqv3ttdw23f7jryjg4dtw83fj2wf275kv38f2qrtj5kk3hg4qf5uf3h + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjaUkyNmo0dCtKZEc3eDNo + UDlOQS9Ya3daQkZVR1ozUXlGYmRONHNCZlc4CjNzSThTT25sSXMrSVY0bHpSNWlX + ZjZTdzZ0cGJrYSsybEtaS1hhTWowYlEKLS0tIE1wVDJFd0ljUHBJQkJ4SDZEaWdo + M3VFdWFDM3J4ZWNUVDhadUZPWUxTZE0KKfBJGHgSv/kl5Bd0tK5YI1+jwnm7798y + 7IaVpdjjqtyHOcyRpa5cO7IXE6sa0GhPRrMGAl8gm2Ea/R+kf59NfQ== + -----END AGE ENCRYPTED FILE----- + - recipient: age1wztgmu7rm4yvdr0x8xucwfmnf0sruafwjx2ttl9cvg54epn0qysqqnr5n3 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBZUTlNOE04SjJ3dUNZeW4x + c1hWc3VjWFZwSlR1by8xWTEvWFA1cVdyL244CldsOG40MWNuRzhOR090NThMdldQ + SFoyc0hBR3NRZXBLN0NXamUrOXdCYlUKLS0tIHFaZ2tNRU84aVN1OVBuWWRFWXky + WGlkcVpiNGlTb1NkdEF3dlpxemtlMFUKRIe8sg9ttBDLyk8M6oEhQeMsYiGq5oRY + hHbJhwRvRysYGwOGU4bKau0veNh5khL+bsTt5TYO2HhFn6cgNBP2Eg== + -----END AGE ENCRYPTED FILE----- + - recipient: age1q58tmhk5hxrfn86qymet3mznafdzuhwl47s8mt3nyn43pararc5sng7kag + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvbEh2d0hNM2dJTU1qeGxm + c0FMa0ZSNUVUeVcvZjFaQlo1bTQ3SUNPSmpvCnByd0ZvelF1TCtnWEVzMkt1VjE1 + RGF1U0w4NDZrMlovMWZKY29mSURKWUUKLS0tIGtpZWJJWWpCaVU1Vnp2dVN1OXFK + VHIvajJpUmVEVkdxOUFMd2wrQ3g5aDAKB1gSIedEvfIqDpuTxXsfamUrGSKRMEhL + oAEZbZ0y4rFXabwhN8EEgTeX5f7ghv6mhcgKLY23x3M+RZHtIAHYDg== + -----END AGE ENCRYPTED FILE----- + - recipient: age1enkp0mlswl30s4h7z4qvyha4cmc2n2exs0v97276q5mx0jc86ggs7g2dyq + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnN212ck1wYkhsZEh1SU44 + Mk05MmI4OGNhczZ4dWdGTlIxbjE3NHV2MTNFCmRobUhlMFhmeTN1VWIvS0FwTXdP + bERxZmo0Z1h5dVo2dWxKU3pDd01YdW8KLS0tIGJuQ2c2VlZZZ29JeXVmVUVvR0NK + T2NrKzZoS3dlaWRHaFdDV2Z3TS91MEUKyCG7yBRk/vTi2mr882ephNzSHHpWFwnx + jxIC2owE/nRf5+AWm8Gp3k7+Et656LHz84UsETjnlLTrflrN8ExGLg== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2025-12-02T15:23:50Z" + mac: ENC[AES256_GCM,data:0r2K+xbmBQkb7BEB+IHwyZifpIJ/hvOe9JC+nPdYaZhodJUXFRNrZ4HGVYkMu15ozibV/03IAoIfoTqJRel/gx6tRU02VhgQGmkYRZyTCCf1sbtPvntxbPhaJnWR8Rbh3cVjamT6vYWyiardztLQpa04XOjvJUNpgccweBEuGEE=,iv:pRIS9DIse7icc60+AE/W9/7tPg8yUXO4cjUoqoRufIo=,tag:4gw0PHfVle5myD5PYXzdFw==,type:str] + pgp: + - created_at: "2026-05-04T16:48:10Z" + enc: |- + -----BEGIN PGP MESSAGE----- + + hF4DfdBfTP3jZzQSAQdArPy+d6Mh6n4x8P6yk7wfmgNyXAbbpTGoB7cZzS9jemAw + jrBZTYMWRpf+GEpB7VVcS33i+zyO2Fzpj2Ot6UG7kV0F8Zlml41r1mxxcaJI5JRu + 0l4BMMuJUAPS11qKH9BI7Ntga0Tf2EXtMpGoUwOy7gh4NfJk08PlT6cKNnpJ7otc + NENCJBI2B7qB+/WRf1N9oxaY1Q509aOF0PqU2Bt/i/z4TXZxfatT3dNC+svQI604 + =9AUB + -----END PGP MESSAGE----- + fp: BFE6386C8D66BCD4DAE14FC895F0FE7CD7E6A022 + unencrypted_suffix: _unencrypted + version: 3.11.0
diff --git a/secrets/cuvier.yaml b/secrets/cuvier.yaml @@ -0,0 +1,73 @@ +wgPrivateKey: ENC[AES256_GCM,data:hudqyQRVINJL1hmCUJ5F8ssgzaPzPjoGPxjpo+ffTH3ecLqCV1zXFmVABXE=,iv:0ebWDEAkPYoZcYaDWSyX4rUZNoTSkZA0Ije85cmp74M=,tag:LQFtefPK82AQs4b5VLcaHw==,type:str] +acmeTSIGKey: ENC[AES256_GCM,data:TgzksmWIFaL7UXcI8h2DpCqUP6bysFZ2Zau5aQkpbxv8rFRyrwLrFVAd9DswNmfRHsXCxjEX4I9fDAoB9Fz6vw==,iv:zXCkMj6RvO/jjc65F58WMruuTrkWTbWqlJUDb/EKPVs=,tag:pb4LOTp+ZPXfaSZWnuhi/g==,type:str] +gomuks: + username: ENC[AES256_GCM,data:fPJFcKJ4sA==,iv:RRqvs4fJkKCcmsJUxFPC6iJttQWiUJs07McX23T6F2k=,tag:Wlpvv8EwGmWVKD1nkaTiPg==,type:str] + password: ENC[AES256_GCM,data:IhU5a1D3CbBb7o0WUGiabVBa9uRR+V7WscweU2MmDAZmWuzxyO2k2n8FElwIaxpJHk/u5Xik4EJldHc/,iv:qzOJQ6+oqfQA+jEyj0TTc675YggkTS6T+dcuD1FRJMU=,tag:va00l7E7hU8xGZcRN7YQQw==,type:str] +environments: + navidrome: ENC[AES256_GCM,data:saoEfReSrwHkuhW27eEYMJ51aVn599G+5q8Kk1/XnzfAUvlS4bUI5+CGfpBX3WFR2JjF8JpgMQUa9/jMHJfRxBOTwD+NsdlxO4mUDe/7bFCTwXHsUx/7MCfYLG1ijwbNBPpfg9sSA+c=,iv:wgYqEP0YGDFrg96ZJEHQeBcKvnAhkxPKuMyAoafuVMQ=,tag:fNpVGrJzD7ZiOpXU03LK8g==,type:str] +syncthing: + cert: ENC[AES256_GCM,data:tw1CytMGY8Xj0TXv1PU/N/F9ItIrx/K/Nw6LsZUe3TZoBBMQ9610mE7ZQ/9Kvt311IeX0Q2qgUoAze/3q8/ejM+cu+79G8YsjS5fzi+Te9U52i8Aeg3kG3XL4FfJ2tzie/z+oqEhXRzYppdJBzXZQLm0wGipompv4gPe/ImMSDoV62niJ1U+scJ/vndK1PTdI8qyQkYSESuS5Ctu+bY2b+t19uCCHGf5/SxhjdZYcCcSI/8m+ueoYRE4+mEWF16zMg0+P5TauCEJ4aHQCbrUPIDTfhgECCO5oC7vZlntf++5YjtkM1a/uLVcR+oNrfu6VZQW6Gc2IOUs3LUs+T8NKJ6jxieGtFgfqM95RFau4ATN/dD8NiSeaIy0FAhUe82Z1GwOq5TuhwANtHGhVgoUq7ebiaU8gWYlENeKazyLEkvwdmqehgZW8g16BFU7qnJYW/1YZanzTqZKi2jw/VnGiQtDx6l43lKOD2Sw8SeoaOjaNScBho3atBuT2/BsLgqT4jcjR5j2R5sBuyZKTB7kh5vLecRSnswOKx/dP+RdNhjGHAqMaJXIsb5qRbOebshWWEA2BckTYzl6xxr66Ku3me2DyZhBkljRJX0ph0EXW1s/CYBKGtsgMycCGT/TO4cLvxsUWmRdWOPemN/HiCD1X9L9Wp8NTX+TK2waNmC/5oVBg/pdA38oC6Stc+CzTUS4v3WgMuBhBTCkRInUM1R6pHjgVq50j2/RIten7aLruXNagrtOwzq7HgnbroKwzThDNB9V0mmOSRvzFt7PBqlUnzrSvSd+XDpedntHQ53FlQlgS1SgmG7PcFwdc65pn4gI8tcm788uvzjgcBFE8ErtEUHLN3aFpHvl1zb8tgckdCN4PtbommlqL6z+/oNcZ8cOhCOx8ZnDP79bFWo4aA1jBvDVBzLC7zdLF0nfjEHwpYuQ/xCBnbOTIDtLecfjJiU9uI5n9tlwBdVpKN5HQuD2hLialXhAVhXZJge0uRgC5WDNG22g1TfD2/M0ghwwfL9iiPmoV76ICjURYZJY7MA1r2bSlmbvZgU05ro=,iv:lpncsQyDe9GqOVkhErE/o5QhFocNFRxirYLEJVrJ4Q0=,tag:jo/dZukny02HDOO6XtAA4w==,type:str] + key: ENC[AES256_GCM,data:sLV8PbY1xaVYh7JJ+R5XkS3fw8D1xFI7trxrGR5Hbf3nMleSzMDbApOXCDZtbFl93pzFK4chg6cd32KmSX/lcOOAWEuZgRlbOloDXD5velbDaWVis9JJXI6xl7ge4m4o/OwfwESiBxYDRJdMVVlBrPJHmHZ+dBn2GgpBT3kAV7Aif8k/RyHvzh60x6qn2NdGWuLLq7T8a2o5/+KkVplqFi501Z9J2uR5U0UMt/dqcSIHJpT4m5+SMV3PL4Wc6SpCVIgIWlIzwL9nTAlJyIQP+PvXKQk+4VIWMATg1b2rUSye3D2SQjGg0CCIYHwOIl31kDddDbgPVVcl19tOYzBcy6pCDv1GrXhx2rXQIKDpdbOpjw6gDeuVI0sw7jn96C1/,iv:SYrZtNeafL9FxHyzqVwqXAGlnlnFeqo63GuWoa2FN9w=,tag:rlIStTgsu6oY2PR9j31H7w==,type:str] +zigbee2mqttSecrets: ENC[AES256_GCM,data:kkFVenP3NrqWUTB5scHCRgKMsrT2BKvYQMJFPbCZudb2rntZxag1ZHut2e1kIvImw8q1bPD9llvnQMPQNdArn2zBGVVuvE3fAWQfXF4JM+J6D4M0Ff89k+VQmUF9L7wgPVk+YtK/hcxjxwaOpv8IiRNC7X4nc7KkLhYSxpkZw90ZqTEt4+YtrhJYK5nmKrrHP+uFoHUlr4X0Cp0t1TyNZbxVW6f+ZgPAUvb/5fbb5j7oE0CU2vSarx8RHcWA4iip6arvzq/YsHZxzWGLdAhUmEdHf9VGPWf+DQBqF5mgX3US1jlP5KwfN912z0VFWuKsV/jsG9LcniMrOzgdBrunxUMBZDpPZYL95yaMeg1TFa0n6Cb/yNBMXk5MzwDKVok5gVk04GqnyIyF6nNZK0GDLU6GjHPpE6dDonWx,iv:sJp6JLH3+VZ6BqEMZwYW6NQNNIk9FLe2ilLdzdeBo/k=,tag:UJzTtdNgSEHd9a+etTEgNg==,type:str] +restic: + navidrome: + repositoryPassword: ENC[AES256_GCM,data:562X0rVly6F8RB7gmo08f0KwtIng+C/9gc6hUKGD4EYfA6NmyjxsEHKGQ6/RrZdHoZLRL0ur9zUFL+NapKMHKQ==,iv:w1iAvPo/fBo30/vn9TP9IHCHSPJehYjuB/jhbUADVmU=,tag:7PkOIOXKJY9W2O+KhqytHg==,type:str] + sshPrivateKey: ENC[AES256_GCM,data:11T91mpZpRztqii6FLLPIvrdDrzvdH8Jh2g5dFF3U42bXWnCJjnUxLsMpVoXRmsqIiPdCD0pPRwOyo0Oea3tw9UmddRb2ls8R+t97hQZxy9yeY/wKLYcxG5UXaD1FGYtFUawA/E+UoK9FwDm1ywj7l7JpgsCya7dAf77+E04kNP271znEwJHruv2mMBhfANRDiUD4T1Z6dNRvcGaPZ/0nvAg4S3E3aplBUUzrU6mFw4vBYecx9BN5Y2KvDAaBfIoq6E5Wv2XrSnWpQSujsxDnrTzMeVe9kLLPWwD/INq46BYOjy+mxV2NVY7eVKn0H/xqqxMcxRGy6PEOKpzPZiPLMOaTCmuvLrcORTY4G3wWxdfuLg5nvwPxBGwt7IGQCBA6ckfAFkP/RkGtzWCGY27oQO/ItB+P44nJe4jVckvSTknhN3TePNAKFzTZ+xCgz3v5BpOfGoNTa/NLMFw/GwvvLgcQtXjOg0YowvKv5IoHayPCoKSATdmR3H288EQRtjNLTuW,iv:jt7QznFjBqA9mSx8sk9PcMCfJuanNHycU+fdmaakt+I=,tag:qC/xxSTlsFsfimoK9AkkIA==,type:str] + gotosocial: + repositoryPassword: ENC[AES256_GCM,data:tIOqoQ8u5Kf+tsym4+qu74f/FTcZrkDqXlG1M+kqb05+xiwtXVu9stCwtSlWgDoEzwOzWJg8xBg8v+l45GiLQQ==,iv:XhQnOFRXnC1tgySeMU1Gug0oZMG+7hR25OOj2WVA8pc=,tag:cwcIptFoTkKp/4NUiUcLHg==,type:str] + sshPrivateKey: ENC[AES256_GCM,data:A/b+BroGLUDfmPzjDoEzyTLre9OQsP56FPy50XmWZ1VikEJDi2OhQhR0Yfgq1kp/kNaJC10IRyIRTZhqErK/WyIjugVwcdZW7R/YK7Tzu8ZFoLRTgq4XqYrTxLKufQMJ2fQpVtO0xKIZ11gNw+lIdbgVxZPVA6nrBFCzSZ6Le13uoT1Bu8aCOOF4U1wDoxVbKiEP9VdafL1rh+XCrjXSKmPECLuVUgo5xBMKNoC+3LWMzlNWz0FGO0Md1bDIC13R+cWTkqNFhgXknVuToX+QRdMvei6XrBu38Jv8WSAPHKGy/fUzsXlxJ4L1iQVeXbjzZ7+kXsBzcg8AfxxQW0ystyFu0IbdmIhypasPyj9XEDGASda5AiS15lctGXAV2EbhodkkeKtgtj4qYihZULE5lvhEPzVaOjDoitgtCgTghClEhkvtV+iJL70sHj8JtHfmOHqH6FUSXJX/BM7AyHBRBXnntgPncW4uWHtq+V7lf0WGWlFM+/MC0Q2ARhWz9pOVx2VA,iv:o0siialrL6gjmTHkh/YiID+nJjLgar+YFuNbXapx7KQ=,tag:TPR7gEnQlP7TCE2QLqrlNA==,type:str] + things: + repositoryPassword: ENC[AES256_GCM,data:nZElU2HmcWnhQf1+7a+qcbzQ4IbPMunj5pMxrglYB+IfePKbxRlOKeIZCcHzb5taUnhpUpcfZMuDmO8MuXzv5g==,iv:lOcNMNi0kUeS+J9i8Q6/uoJlrhC62yRbSRDc4GKKZ8A=,tag:XXjcBRro17i9ppw/HzT8rg==,type:str] + sshPrivateKey: ENC[AES256_GCM,data:a7JsWKx7Z8ErRvWihxG5UbV4rP1fFavorLfoVVGUWWBXjrkiiZhdZy3Ft9sIggyac96MBsVArR74xPQgzjMyYGLmenVsPGxUmz0nmARoqAY8n6wBRpHC5uRC5f5qM5CdH27BkR1x6Eb0F5W8gyadfHfYmGyeBTXfWUdmWQ4ITOLp/Pto8Qd8YrXANJqq6yZvuwebuOWNckkM2JUMkKlg4tqXNYHOv+TFmcZ/SremxuVdCuXUHmdn7E9znEpEqYEjPsAjO+jc+9KoXniFd40OYyp6/ppDny8fAUsfBO6vqdNPC27aaZAUrbfTeUbmEIcscwAOg6oTAVU3MHZDgTw8OJhR73mmn5wgy0LtKVFldybXbKwGlFYrFGzLp2DDFFaJpvbv+SPvCGaFuTa7Om+/mRxMS8UK0wo4ahpxl60POffj65PlN4XQjfyhXH1jUkwf7Z+cBcCnN12fnr0g2IlrKrUl2mwjMs7yrlrjd9/+gPH5tolqBxoCDRg1ZtIAED9ax0T2bNDGsv9fsqNBPaTV,iv:isMFFD3eSpyomV3syJhi7rTcwsNRMrrGkGhbym6yP1I=,tag:rBkHMHUpYhPrMHPG6TPCAA==,type:str] + immich: + repositoryPassword: ENC[AES256_GCM,data:iF466mRAzG0k4lLFlKowIR6Hy2NjaFh6QJwsOeHZtHGJ7C0lxEIuPRPUiJ0Yrz7fYtGC/Em90TX3+t0f6IWANw==,iv:891vHDFzYz6/wI3LaAgfCKbLnKvoj929MSa/CvFP6TA=,tag:/JNiC/OVM29EPCS43tCfhg==,type:str] + sshPrivateKey: ENC[AES256_GCM,data:GvejdgJYWXm5Za+OhPpIoUujtDGHy+F6L86L8YNQUxP+38X0dMHnXAKWhnhOZZ+iO+ny6pjKYY+qhAZAEOAP1xmPHkVOHm4pcXIoL4TZuhmYUzf334uon4krgZ7kS2Cw4C0xMzQOyT4NV/0HmvsptiJt2bQNdgpFgzgDgi0EnqrmuPxvo/TUDviz+g3Z2zZhsupgBS83pdvAQxUPEWPcFR1+mIoEcmNXEY2qjmFNPi3IrXOTQ3Xo8RhouKqpnt0IvlpVSjMCDGVtYkIwdD+0pj3QNe7+eVMq9zG+FHptI59wj6UiJOUIsoSxvEqqN2DWIwq00pDnDJyp/NnAvZB4C7A+L7O4nxqwH4f2eYwlIWaouSs3Hz5PPfqduOL+Oi5whb1MmHslpGe+kK6AvHbowzy456ZYC3MnOFNuXNZMjPB0Vwdtk/BKNWZv86GWZbqTsBT+lxtHivG8le036EvMEANrBP+hDt93sZlntosxHqRNAmS2rm3RmToCkpGFs6QZaazyDyf2UZWTnYYDWnLE,iv:eBawBIa+Wdj9c/cUK66J5kQ2AzCoqlBXM8brzAHlD+s=,tag:egrihMdDqBpL/rIq2Yl2Hw==,type:str] + syncthing: + zaphyra-documents: + repositoryPassword: ENC[AES256_GCM,data:1Gh32TQG8KAYeyD0v7GJ7cRCRfeEqIwcN9yMOWqcrNi7ZnW1KHcnY/EiAeqimKLUjxslgwp4wchReJJ3/rRdCA==,iv:HJMmcpXzYgU8dukMXUkxvktjndGzGj4PaDNzMMUJJVo=,tag:jTSTrRK02VIKwmUHpxyonQ==,type:str] + sshPrivateKey: ENC[AES256_GCM,data:UKP6c9IVMlPrro/aW7y/RYCi0ORstDgh4McXutxsopTS6QcilFzxPe1GrBYrEJHAfgxowIvby2NdAtmcQhDAiG3q1tk8cYGkO1/M+UUU+e+UcY6KUBZWO5cJMMfunL6x80wzPJiY6XaXOPazaawSHWT3Lb8oKehdOdLi75vX+6RfawSnqQd74x91GORULjqQgDOXSIJ3r4nGUZzyh9QY8tPaOC5XaL+gTyNrkIlzhc3iOdS1tEPVpnS4TRi2sppzR+FTdEC0VX3pHSD2UPrQb+i5EXMLU54EIBsl7Shi7st1dZxyQgBQpy6b5pgKgfjamrR9QR7v5skTBYLFDP46cDjnYjEytBHbDROwOs8/Lbj1mQDUbS5xHh91wNmfVUJdwVrqUNiE1Y0n6LgxMnwjVmGjr2AamrFyZEuIRChxADlJifITW0UofhrLszVUQL/WYeIbkHA1HOlldnDe+tGHbKmzPHGcrshOLIPTe5B/9xQ6UJ/FRBeLu4JrQzIsbFzrIwIPMkQeqtmjOTBnlFN7,iv:BtA4ohklAuN/i3LcZMTGs5OplXfPaJffchVHsvt+FOQ=,tag:uxx+U3JwxqqHp+mCHmft3Q==,type:str] + zaphyra-db-richtlinien: + repositoryPassword: ENC[AES256_GCM,data:8JSHVHRSaUJwxqPAenKkk7bKJ9A7ya7XUBE04JF8ocFN9r7UIgw7+5tbYVGM3gK6RB8V1+uWZ0ByzKtKj1W9jQ==,iv:znXbWYY8t1nGKO20so8tQ60jIrEEFJ+cW6eitsbGoLs=,tag:pjyRPnQ3CpkwRpHK0Tp7EQ==,type:str] + sshPrivateKey: ENC[AES256_GCM,data:UxR5u+GmH7PaVrrtIl41PasID4jQX0r0zaxNAS+vrWuen+nLZc3hEhwmViNrRhr7nlVo442HxE0e9QVWJig7yKGOhPvnCLmmcuuhG+lc/uEpDb4slu+WdJsqMRIVGSAbgm25p/3r1ZpqPUDZGwXXgbSri0e9JsKDq5GVFi6+tp4zKSl09OoziNIO00MmXLtLV87UdEJuoA1yLKBR7cUcAUXXExar0kP+yWRiQ9hGmNEGvzpRCJi/OWN8zZVFPnugTUcN3qxj7ydSK7z6XDFSvOmIzxb62NZXyRCOOHVAwhYZYAQOtDrsrYh0iNYQrnnRmi465DgzKMmZsQUl6HH+3QUm9ovKYQjgHUZwA7wEpu7LK/h+N6EgBt1ZAHMSeL+jjjdvC+lMSB+BkxE5kvbGefZuSh99UjsTSBWGVnHx7Lv1mrd2ol+qlonJs1ZcTI3c956pmk9NgCy6VQSc9PN8bR2CYwlgaG9zvfJf3DI9Efpvi6u7D2hGQyloCpMEwlBlGWLjLVUHHN/5RYOUeei5,iv:QRIiPekSm58PCWBz9BkV93AuiNbxRQ6QQkicvNr86Is=,tag:S9SfpNfOQYx4YCWmT9wBjg==,type:str] + zaphyra-pictures: + repositoryPassword: ENC[AES256_GCM,data:naVYZxIpcKTyDPjouzhG4VZeqW3rRBRGWmFxN9nT72llYcZHq5d9R//KVg94G2GWaB6zvJ9cJLlBzqrBPG3Brg==,iv:QIM0Isfx8M9TO9ZI5lPQ/Afp7+qQ2H/7roL0ydZJ4Q4=,tag:vBP/97ELHaYwott8DzclyA==,type:str] + sshPrivateKey: ENC[AES256_GCM,data:V1oEnmEDQM6c3WWNSAnNpgUB0+Qzbqnln9oNU4RtcZwWDdpe22F0rNESjRwUU3oDLlP0tdPmIB4ByLxb8y7CcyfQZENMYhZGysWEdqhohoTNnSyR1QdH1ipIr8JCn5AH4irwmhFDWu0cbbVt44k2wJ7FX5PJF8JNFCau5SGXvazzqoBKx2zg/eoYA0zjz7bcXtAhpwS2JlhbQxlfQAuRypjF9w0TLd0ttZELx3D3MHi2Gdyy9B7xnVpVzFAvUjSwTb+v1l+HRioIplaWEcM3Or4wXmUOn8ob4GpeUsA5aInUo+M556j82zmrRE79aT7VNcwb3p3y4WBsEfIj0u/yf9yE0eLEiZzl4uQA1fPcXU0QGh8zctD2sEPUTyhN5YL0X9XSXYzykWWgtX+nex1QhfsKHVoljNd54c8Grgt7hkzQB34t1FNaOiyzln34mQxjOFuMLBdmWCd/h9ykxFuLfs/AocKRHJ2MWP/0hK5lJLfnhgBmQN/Abh7Vo2Eg8dfjIvsA+02qC/oW9oQsfU3a,iv:Umwx4QkBTJPW+SVTXI+PpscHgWiSDq7nRCbTZWddx44=,tag:cHN1RKEhCzWtiNq7xA8+cg==,type:str] + zaphyra-videos: + repositoryPassword: ENC[AES256_GCM,data:3fFva6HGbpibzSr2btU9en+maLsMjLnnelNpCGL4cFyK0KyGOTWWnWsPOHAfVbGQDy4KYNpyJK6I4i4LPyIezw==,iv:PErWSZAgw4C+136RyB7uUxI96AWen4M+0ieIJsVm96M=,tag:PqXegP8vfXjJ4FJb+UiWyQ==,type:str] + sshPrivateKey: ENC[AES256_GCM,data:xVewzNcWsXtTNJstwyMBGClUowqoZvBGB2CpcidarOBEHhi29pmWGXOvHDtYGGIp/b9KXPnxcEURiTxWjrwRVYxS9XC7qmGFn+cyt9MR9tcL3Ze29kWksUcPsNtj3Km8NDdNZsTufA0ZptPWZIsELXycZX1qk/Zz7PhgB95uCcphodVFCYBrDJegXls8OwE08xQn2moYN00UcHMxf0kAc+BtC5I/+MNFb4XJE+wZ0KYSNKfVjn5uk2Axe1t//CyvexE1Dw1whL7rz5eJwxUpbozEtl5ugJOMMWylVi7KdMu+aN4PNDl3Q6jBrhDlmHenjKsTk8eXFfEYH/ifRTLcuUfpyHWo/J68N91R4JqFlHjlRW3IYDagreBcDznbBf2TSIRYXq5zArxfQzCGmktIBW9uDh/uB/8QyVLF/HsJFLF3pYslAiWBhDl6sqKMGSJc1ZileRvJ5uO+nUsScdbEK7/L6AsFP/DBovRa5WTg1XI7gDttVZityqTC/oiscsYdyQtYqjhMmJFvqGPAmtSj,iv:nX/rqv7z7rLCA3K7sut9mlaiS0BVlXR650I5tbNZg6Y=,tag:Fz4Isl9L08UlobjYqYZ7rA==,type:str] + zaphyra-audiobooks: + repositoryPassword: ENC[AES256_GCM,data:b8zh/E/f5ynraUopZyuj7ldnQvcFsNvlsGS/g/vJrWz2PyzbZQ8Zh//0ReCXQ+Ulf/Bcv7YHUikb1bFA+sbpog==,iv:pLY9zjIpeuOMCaFM2yevpkFUxNc7fNNxsJF7Wvssab4=,tag:qW5ZqjKDfRBRdiQHDCrTqg==,type:str] + sshPrivateKey: ENC[AES256_GCM,data:a0973VDn5AmYbsT4Nvc33h1XNnTQ/aLYpRcXxPPB6GGMij5fjk1yaToOyWdtADz8z1sE/9bw++4l0m8QEdSO3BKjcEZn1mI7Jk9tWRfy4esahe8TFDVqjTRbrVCP3CpHfVAByGy/1qG7jXn16pwvFHVC2tf/jGvznir98XmVb60+hzAt2vv+cBfKtZjmFPJ7MRJgU08hKFseVTiCvPUM81XG7U7MiQENvkrrq5+GX7RETsEC00KtQpkd9hLcAk1/UPDGM5mi6bPrH3eZ2orb+81piIDl3SnVTmQCBZZeV5V6g6KWI/iv+bQwn25ZdhOge8k+UeP5fGJ17K2FDenPGph8PSuTkgcviLurujMgqX6ACZV7id2U0WSmj2QOyX4rHmRNZ+SzTV3H2MZrCBCTVF6iMlvqjg3z59WOcSnBTkvSxHHuq67IkHctjX+lZ9t20Jw7PZgMqE7qCJgDaZ9RUzNNeNQIA5AmMPPBxryGNPRhcSft0E+HWwxnYGY1+xvjudT1fG5OclDL6l6z+7wP,iv:urcCRSrjlCHmAsbdXf278PbyEqk1Jcvaoy964TR/m48=,tag:+CLoCSUcYD2w4zffDYnsfw==,type:str] + zaphyra-music-orig: + repositoryPassword: ENC[AES256_GCM,data:yNe+6uLTDhbPzJeuPCACmR1Xi5lzYIWANzW/TkVHfgXy+A50ShFiFDXSith7rouxwy8xoQMgskRIf04xUgnqfA==,iv:UUezW8XBYMWY3xCLigel3UO/Py7v3A8bbiiLyPoJknM=,tag:pk4aoVG1Uql2RN+7Mlrx5Q==,type:str] + sshPrivateKey: ENC[AES256_GCM,data:+TAvJSSMfhRWM5ENUZH6/Lo3WtrYnKd8WAwHbGFRmViyQuU1H/XKkrQ6ARPBGH2/UmrfQpiUxHKzLbl65pibrUJfYOfeZ90/NZxBJLT0cROAUJcQScqoISIUuurflK065ohz//rX99Ixr9vGTN8cMT7Z56ALRj6bd6xZ8iNSnc3AIgOP37LNTnfuB+rsPzHm4FukZWkRENtqmUVjz33ZGPdqn8dvF7SQBX1nRuPcSiVDwfB9Tk9K6fz0nmtCtUoVA26o3lumZvYUFvB8K616JCU+sEN0W1zWe59oUNTvDyg/y3snBI8TyeREVaWf7yuEuMZjlTmfu/hJJeTNQfxP5mQVnkwQx08v+yZ+MJFATAFTRZAaKjaVZ3Ojv6LyhliHJL+FllEWRaRZBO3IiebkmxDDCLhN1bmYUYydFWkXOUXtwkSB8LNZqA8zBH5kSSC+uVBcRcrIOvuKHxIRhnlS5aRigBiN3be+/5PBfXsspgFiQdqVliEHJoCML2dg1ELBqaeuDogugKuWi7JnTpN2,iv:/O2FNSdYrqa0x66zqeUenoA6h7FjT4tDZJkGdbWLLhc=,tag:bj4YfvJrD2Q3MTyz7WywMg==,type:str] + zaphyra-media: + repositoryPassword: ENC[AES256_GCM,data:JiFGGabPsgoJMNalqdNBS0orccNHME1LwW/70Or6cFVS/J6pMyWGIljZfJu9scGCWGrKia5Kwidy2qNZuqFN9g==,iv:+pW7pULpZMBw1HLpK7uJh6CkxGzBkwAvc5O89QkIdJA=,tag:ugFaggcJFck9cKiL6hAFFg==,type:str] + sshPrivateKey: ENC[AES256_GCM,data:8bZXSDMHfNN2+1/CGNArqA8RkzHbbG6MJhz7l4KG2PEqFxkDl0qyCfCSk7Joq8r19cvz3AJZd/UqkRv/ga2vQg5oxsi+u8dCzZmdUCczWPIErcd48DIHQvOYDSigN76dMCF9idyX9StEoqUPpfIxIfA50KLGOtVdlLQqOKDIXSq4Zu5UVfG2zOUHCexgoNsT6QHsBnSWTXTzbsgdG3dobRaeXIn8aEBqmHrZ18bBHpQcf/xhEFRr9kjzL5lkTxR2zrcja7wvXFQ9q8/zh07y7aXosFnpYcMWp/ysmSgk2xo+PTYm3aFQHLUjuWJswJDOXpnjWVvwlpJYaneYU2fo/RFHPpih51o3W5xL7FOYNXqfo587KIgATtkfFj6Sy0GZraN+PQZOPeIM73Qdzyu6PBx4Nyr4z/aG3jsLYbczziFI8zAoRiRBsfs+TPWflZWFTkKCOiHjT5jczI8JmETdXibBfKwMV3nbaJhjUhramWyMyLXd4v0c4tIBOND/tLRFSOOyZ6kJVOq4xN+2wKk=,iv:F1owr/igSXCS6QEUHLv9pVLcoc7kHghcvWpNu5cS2XA=,tag:d3VeiGO5DQnsTGNFx1Xb6w==,type:str] +sops: + age: + - recipient: age1wztgmu7rm4yvdr0x8xucwfmnf0sruafwjx2ttl9cvg54epn0qysqqnr5n3 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvUThET2FpV3BjcS9jdHZC + WjZWeDlZOGErb3FHK09TMXBuUENyQXRrTEVvClFINUo3dCttSGkrNnZMQnZNcEc2 + NElwalF5VURkaVRNcXpXZlhER3A1UG8KLS0tIGNJL2VCUDRsanJNcjIzR21pMkRS + VnpNTCt3OUltYmRuVWtzN3lxdmhGdzQKhKCLyIW2Hb++BiBQiF3WaRUd5KmYkjea + ZdPTn4IM6RaZJrmIJ9sViciFqUZfJ4EwKDnPUjwB82leJ167OHfxxw== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-05-16T17:25:04Z" + mac: ENC[AES256_GCM,data:SGvyoQcP/T9glulzRSWH75bpF/IKqB32pB4qdquk3ADdj9tqxbIGSBbabjxOAvVmARq3cRoDyAYYQ659r/VxvuL+u0NSiGw3O4T3w5k7Nl1uc64c4FI3ee7ePQceEld9mU+x7Wvoq7Rr9Y2ltHh5uje2VeAW3IyXdLaOxbLkzE4=,iv:df/Ox4pNObbVw2mAfhUSLVkhnA4aIUh1jzIIz1YYuCU=,tag:7hduOgc1ee3aDbPV9aOGRQ==,type:str] + pgp: + - created_at: "2026-02-11T11:23:03Z" + enc: |- + -----BEGIN PGP MESSAGE----- + + hF4DfdBfTP3jZzQSAQdAH7W09cqt3rqNgveeY40tlaoNDEvrh3JhyBDuj9BHJ1Ew + U9u8mOX2EedMwUDuX9OUI0M3eirQc9mnbZSIPFYp2gi8zWyM03hQoXIg0JLsETre + 0l4BYFe7wy+b96AFUYAUDNHyHyZAQzn+M4Qy1qA8qd6ykEP3m6ZowwG7AYXIQzWd + hyD6j3MTbC1g8E0YiXVJ+EAFmI/YThE/2RjeiRose/iQPE8bMn21JKpuGk0SXY2T + =KQFQ + -----END PGP MESSAGE----- + fp: BFE6386C8D66BCD4DAE14FC895F0FE7CD7E6A022 + unencrypted_suffix: _unencrypted + version: 3.12.1
diff --git a/secrets/default.nix b/secrets/default.nix @@ -0,0 +1,21 @@ +{ + npins ? import ../npins, + lib ? import "${(import ../npins).nixpkgs}/lib", + ... +}: + +let + haumea = import npins.haumea { inherit lib; }; + +in +(haumea.load { + src = ./.; + transformer = + name: value: + ( + if name == [ ] then value else (if (builtins.hasAttr "default" value) then value.default else value) + ); + loader = [ + (haumea.matchers.always haumea.loaders.path) + ]; +})
diff --git a/secrets/isodon.yaml b/secrets/isodon.yaml @@ -0,0 +1,42 @@ +acmeTSIGKey: ENC[AES256_GCM,data:qDk3XM2ZqPqdAbGTkLO1x/ZYK3JNcJxQReYSFz+2Mcpr4B8zeM5Clv2DYZds4g2MhRZngx3zCTabYK/NI3SXlA==,iv:oAP9NmLpI/5dbVjUT0Swyv5ARheY7SYi21+Bbhn6jLQ=,tag:lqjWTXma3Zs6BDu7iVaLSw==,type:str] +wgPrivateKey: ENC[AES256_GCM,data:SYGGcLVnUQKFZYmpMSIggZGLek98pWmOOdtUO3VANDoIdYJgXJO4U6Ntucc=,iv:NSSUtB284+f0iMOv7ykak6xKJXoPIfoD/PjP7yWiVl4=,tag:lmW8oLaikrFr2vrh7GUFBg==,type:str] +environments: + ente: ENC[AES256_GCM,data:UEc6HJey63DdcpFyVy8K8i7CCRs0EEQzq/DTUIFq56EDfr9Yt3kG7qhtxlSAcflD+jKCx2LUoFAoQMEgUOX9jXp4JsQdPFCfDqnbClM0A+BOOXcz+qIFVD4jCmzcVDe243c8AnUzhKl88qtvkzxnhVcdN/etB79RrbxP/eHZeozAf0LEr5VdFwhyvAVccL4Secegfh+Ug0oUlDDzfSV7+c+vHGtBfOj6HIwWV6F/LhAzfbBdid0yk0ChXVvkszowf/dDzR67NuDlh4a1IVozQNcbnDLCAxCpwa3+LNoE/WY+uCTXYrEY1FSZnZZkFjRcNjPfNOGBobKB/EPyxbDr3BPReVYBo68KCRhmnri7wdThG54MGj+MnvCoutNM+dEyNwcuBjq41c7KwjFrSM7Qwn9uLiH8FutU+uAWITz9j8MVUaA++UBlHom3BJ7l6EUVHjo9QXSP,iv:91B7EVvRJWWCls26i5+CIMN9GroGfASpnv2bf3F78MI=,tag:R0SpUtbylnIkmO7LPCUQuQ==,type:str] + minio: ENC[AES256_GCM,data:ldr2esRCB9j/FsexodAKBpW3geOKj3mum8GKI3JF+Hs+Um2uMFbHRIamE1Ag6NyNIqYzM2UWsF91baSnDZ927BWKsUxbh2iPLipUTPIkJq0RsSfj4EL1HtNIXsqYjIKR3vSmS8gQ,iv:eLGFJw0/3A2pCZYBg7n2A65f2PG1a19j/02STvTXEZU=,tag:7hPEC/mN4SJH06pxRLD3rw==,type:str] +syncthing: + cert: ENC[AES256_GCM,data:qsaUGpfioRvylwpof3R20cbmIto4N9xQvIyIWpJsEwu11b/ikiFBSm0gp3EzzrKXCcoVkIlHTPQeYWfd42sdVoPRj5igQ2stgsR2oUD8Nml5mduu4mvREis6pd9RJkUGhQcze4M3l1zbwiksCMpjdmeO3wE0wTQuvOrbR/Umw/0PNkEv8nVt3eFwFoDTYEyJ4QiicLeF/pQk2akB5iBn+lK/0U+TjxxKfHzXrKpi/Coh8P7RuWb3+OOB5cq1dIB9egZO5vrdo/QbrskbZhv+Nx1XChxL0G5j/1v5Ijrux0jOSf5Shk0LMmQf4c+trT39X1LlOIA0eHQ0SySWSs9jVCR9gQ/xO63U9/DBvZJ/+YJkoGFOi+LYxm+OKOJLt62d2mCWdV9Wj62mBM3kPHyerDhE/Pi85uUp9ntPAhZooF9bUjuVBE8cmWH8LvLm6eE5aZFWV/Gnfi/FgXfUtLy2xgv7NGqr+5FfWRTH99ovkI/9R1+NZreMAUHHLG7blpyE6C4LNgLMoVtqXWAxykgsiocF1u01DUzaxXt08LltiBOZJ93YjWwf55WBtJYtw5WKYf2mEipG4krdxGSF3Nm5dDyA/5rOziUMLx43jHyu8v57zcf7ZW5hC5MeMnnVpRG0qVfm85wyzLJmyW6fMfgNeQL/i62T82x7SeR0AKNxOiI6TLXxKx5qo4L+jQURjjuYy2xugaqm2K7ermWAK1p2euyr03SDIJApxt5eTIBwjspTaoFtBZFb+4HPawAaTRqB3q9HBD0wQqUi+hSpL3JOy09WyH3GtKRFn8WECfKPCRImRdEG3UKB2YiOT/bvyLRks92YLBG1t55SqACO1k7y9f9d5dSsq9avS+a3tSgOIB+YtumFJUwEV6aYq6hFF3PwMbTJmZwPtCPgk2E9b/tN0syRbnHF4Hh9D7dglHM6GTzo1TmdlBwGECcV9tra820rab1KgcFGlvaf7yDCU1NNmdm2AJvKRgjL8NZ2ZPv8f0JHM2U40WnzPxfoVT3rqTT14ekvXQznJbzolO7GgWhtnyTlX3dm5+LQ81A=,iv:orep2lxLt+qUGyloMIwjr9C0CgARqJMoKJCO50DR5zs=,tag:sGlLh6nYKvhbStbLyLrH2Q==,type:str] + key: ENC[AES256_GCM,data:S7DXVxsZrTE28fnSK2kwFFe1gVOGBxO8y8z/MDzHT9x56RcWxpY49ol1b1gzwRSW/IKo+4BEcGpiIT2gGqxfHpe6wjnc9p61KVCZaImXLQvOTOweDFYpzsnwxPHFbM/GN9YoTsP9pCBF1OpngYM8H0XeCfVsH4i74ndnpp+raGqok3T+TWQKgOaDLR8E+EstJEAAvZr0B/uTV4n4jY/X9p0lRFLMSF+1BmKK7Edk3B6jzmbz8kP1HTKxzoI9t23TTVUC3CMD3KCn2tp5OhoCtPTI75UCrPKohjz/g2ZlfbrM8vpSV2h4RCBvABpMm7UayPNGoe0AAWcVS2xeBOq6wWh+PyYLNdu+l78LOCe7AyQrQbok4UlVJ1ql2uxVqZJD,iv:HBptgmPv5tS6G9qJaRsH6KtKpL3UyAZoQJqObGBpCxE=,tag:SEYYJgHRH2IIo45IgNS88g==,type:str] +restic: + gotosocial: + repositoryPassword: ENC[AES256_GCM,data:9vknU77/Rjh6ScJZDqv5Gss+9Q6pEZ+plXyQKNLeqsQnnpvjEd15NyqtFpj5IArKieHSJ+rJO2zFaIPsrkhiCw==,iv:GnyOuY1daKR7Esca29Mt/JDQyoL95bpjA8fmorhjsvI=,tag:HMKeI5gdnBblUdTvdFB4jw==,type:str] + sshPrivateKey: ENC[AES256_GCM,data:vr/VGxEAa06DMoL/VA46wM7fbI2ayCIhkFODiW33mL7qekCXObLc8EMXZXcEWoQlgT3Mpy21bMAo1GV/D7FDuZS1t7ISs3pY8/8EptfEmABA4Rm2m5vPZlzfHICOIoIJG4COH7yujSG15TOtjCKr//z+dcRzY9LGTse/pjieHA932e0Uod39QA4unHzTgvhDYiiuj5nEwL9qAiRIgW0JTyOypxMlQoKa6Brx3NYgH9swbwudIH16uZpfj8vCzjuWPfvxAeEeW67aGl+GPz2H+cxO24Xv+HQYsgHz/A3JROw4lREOh/I+AcvxTyHSUTw9P8oA/+ffcfOFVlzAoHKmA2FbfrF7FqbAKehHBxxrPvKVREzfEChSnKJOFIQUqkfqK5/n9i/MM031OmEuDCzvO2ycOmrHzOWeBE90jt4whEH/oFdGCXchdOBVxn923W2bTeq1wS3M/NJA3Xo69YIW2NNB5jr7V1vNpXGuRaXLQo9qE5LKVqUN3TDR9xDDoKEbADToHBKpdg4ohGSFqaZe,iv:HAULIQwRnxL3DeRxTTblEMqxTmm9QzdpxpK9BjGZXRA=,tag:vCKGQsqLoMWcXzOcHgDPpQ==,type:str] + navidrome: + repositoryPassword: ENC[AES256_GCM,data:c4iZCvyMI/WSvUOfkqVs/MVnAx6BRup0c4yeOUhuH1GLWZln6/m78Fvbzf/5C6kHqoexx8ml/3m2ja6ZtPdwtA==,iv:c+HaRUzkqHuh911ygmf8O1qDc7h7sxbM6kXRh8P3R9Y=,tag:8PC4cxfj7+5n9FgrfU0v+Q==,type:str] + sshPrivateKey: ENC[AES256_GCM,data:IeBsZ8wXcm9Mw43DrREprw7pJoeY6AqnMjkTKZK27R9B7GHa+rTPrh7h+cDWycpXK+c0IgloH2LeZBnLxTmNxUwMYVFqq7OE3SMJ8LUEFvwvjgGOt+WvUqB2/AWxqReYdiGcydEfNRaCX2tpX4g3hXHl6b03/DUe6Gm7derAQSIpOvp4QJL72j8qAch/8vGBTuDuNhXzzG9AIWZMPPMajOeIOIT0EH7WVUwDNjSFEbR32wQ9ZKuKAtZpCbKxoo5bLB9QvO4tmAJBvcZgW9LWKZ/5IpNgqf7dqQABjqINUrdo1+K4B/Vs4JuTSA94ua2x0Y6NoxNSUJaSrn87/ERde9uWEBBH0m+TFemGAzEdoU/ILw4QwJnmbsw/cXe1KBkhuHNlUZ6+UwD2tjApgOb/NDEhXvwIL37JGIb1nxayM9223fGLdzi/e/futIAFGzbkjrMIvWuK52D3kyi1NxNpqv4v3IoFWLVPkgDDfzAk5rlBDFQqgYUKHeyfrmEAiXDZsfwumjRQ6/0huJHTGkY=,iv:mqLnoqIx4BelFPae63Ilf8AP9qu7zmrhkWcNKkSMWIs=,tag:KBgs6eynrPUM/qsRObOywg==,type:str] +sops: + age: + - recipient: age10ujnqv3ttdw23f7jryjg4dtw83fj2wf275kv38f2qrtj5kk3hg4qf5uf3h + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4QkhEM255Sk1nQ1RFd1Bq + R3hLcldmUGhOa1UyMCtFNmNVVDU0NU1HQkM4CnpZdWZtdXZ0bndReGpqckRWVW44 + cGpXb2Z5SmF2Wk8xODFtaFM2VUVJQUkKLS0tIEtrZ1Y5eFduUXVyWVpVUDZ5dkN2 + SjdmMEovakpNOTl1dzlhc2JFTzNTNlEK0ugZnzsj5hWD1SBMZYZk5rEPgvS7WlDy + UAPa/Y06FzG62Ku3rLLwZuF4vqFekx6dzo3Vo1F7hUb+Xz2asWfNWQ== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-02-25T19:53:16Z" + mac: ENC[AES256_GCM,data:JuqlibBovsR42S/f1RLTrXWDTTSIlnf411YfefAxL1EE6sdt1aQbZldNlj4TthxILP++AqAhJYtI3bbdS6cGRb/1FLMqGUrGS5XlnIzFmG3Uly+tNE3iORfJvb57dp9jIiXc1Nkjn3w2mB5Q9RmsAcQDp0qhQHBKPRJGQIx5abo=,iv:EzrqXzFN4q1KO6bRsxiY5Vb5D7juUHuRNRdgvL0RLNs=,tag:AcWQFrHYlnEH+zPwFHe/Rg==,type:str] + pgp: + - created_at: "2026-02-04T22:47:37Z" + enc: |- + -----BEGIN PGP MESSAGE----- + + hF4DfdBfTP3jZzQSAQdAYfJeJ669XAo/QnNKKRONlvpQ50P2zTSu+VkLj7oScDMw + M5Wf4gOHc++EkR02Wqk+FUFYfx027UbKtuPhnEiIFOm38O7iloJ2wCuJ2nCpUbql + 0l4BO/4Zur6WWtzTO0Ui7iOAjKLFP6qAmNSAQs/JfH+EoQGU1CEWLv/u4+XlYQVg + H6iEmCB9FB+2Rq/2cKc1FKKYZPC4aGXRfzTRGLSzYK7wckZ6Xsk2SE/iBoeQKH7E + =1b+W + -----END PGP MESSAGE----- + fp: BFE6386C8D66BCD4DAE14FC895F0FE7CD7E6A022 + unencrypted_suffix: _unencrypted + version: 3.11.0
diff --git a/secrets/leucas.yaml b/secrets/leucas.yaml @@ -0,0 +1,32 @@ +wgPrivateKey: ENC[AES256_GCM,data:Hvp9TnEnuijffncHVU9LMNlNL5jsrdnDkFlDbEDPjpfGeZ6jCvx1O/BUGxQ=,iv:FWBUEtNfotGbZRoZj1xOvkiv3NsUouTRLRJy9F+v2nI=,tag:krdGEw5i7wysSD+i1ZMlVg==,type:str] +acmeTSIGKey: ENC[AES256_GCM,data:9DaTH5dlV4aMIPI083ptjqcPUYpiGu6L47TsMFwyQtVviJcMvhdAQPSL3oMt19jGVSt3hyt4hwSnH2bFbQBvTA==,iv:F8UXkZFr53BjCxSfRmR2ja1AmBovjuF9Kh++atJxQNY=,tag:Ps3iVHcAi/Efe0hvTaLEmQ==,type:str] +syncthing: + cert: ENC[AES256_GCM,data:8HLutfobxqw9AsICdcUzZpOWyBaUaKEbJWxWiR5pDGpnrhNPcsbdpm0sqLNrj0HdHhMdQP6kEnYdiC7GwgEXKQq986mwT//ETD4mfNf/LVHEJKmP6PhUfOSg3BqIOyj4Eh+euySv2anqbbtFn6zCJisVs/a4Jd6jw4ZCfzN9ksAqXmJSllhJe/QGX+7r12rq74vM9z0NzUCKjfJB1bnydKCAmK+PJQg1l+sbOQsjah1IF5Ekcae75rTVs/St1FI85edKuzEjy0Jpe1nTJZh1D9utfdnXh/6Pmwpw0Nb5Sudpp6+98/chn0pH3Ytka9LAuL2AMi3Ku4OtuWQ8tOKdf0l1atUmaW6q4FcxyGXfPnGLZUOKqYBQNBWPDfJV0vqcxLLPgqYIw7VaeIGXnl99qqNgYs65XNnzCWLL7G8r/vOS5iVEMIFzpcD4FxYP4QWnCW0SSWnaHHHy8JuELJdPPLuRjfCr5PJ9AMJJvg6gB/ZxSaz2hduUwC076hLnCkPxNc+OY5oRC7ksp4qVLJE865bWLeVg9PaLlEfDtBlCydxnv84iSAr7FyUZ8qVnhvWZyJcsxVK/putoiY2QPq2Joitvh+VafE4dCjaGfzaJ9y2gKUJOc//AZpbnN1x38puiX+sAuwo7MohOysIyjWLpqvN3E3UZXyC6CJbPX5NNYoHIGAWDVmArm88vv0l6vLkE7u4HE8atK+Q6/iaYoAIb0wxZuVDeV6lNarqMjjDrd63D2DfP9+eQd9NDT9+QPO3CUrZnvfWxDv3qdR2koX1hXjUoSSmRpCIjuMagBTxEnd+yrxG2D6yb0hA+Ph+3Oh/Vaej5+WT9+HP2F2r3m51wLRFwxWAWw+Ug08idMxykh4eX+XflWPtnqRUqt+HyYMUkadN33MTjKYGzqu9e0cRM6LgnoUsII7xdwquOc+mYAwclE2axF/maj6KmJesOUuWu/K+Gvni6TOVKwgPzhDgHthsekYVZd99iwCiugDmAmALCVV9xhtrg034R874dy58hfV36HHUlmB0BDJtDn+vqSbSsTHAqMB+uIh8=,iv:EleAtg+ncqhC8sZu41h8otk2SXNMDW59XtKx0CozbHA=,tag:MqEfsb5T9H7F8uJaqcw7yQ==,type:str] + key: ENC[AES256_GCM,data:XJNe9tsiItbqJ+GyTUm5lvWW+K0DYTt8gYySPcr6+O4vp2RGf3ApxW/ET0ULjVeKaBO2Io7E4Rd9JZh1T3lcHWR3NyWCTCQf+qpp9P2PT0fZfr8t5KoCh9NiSuWAm2iCOkbe4kWtZAnXJXX2snZD8TS31wozjL59SkU5x+o/MY2Peq0gAwkpCYBdgMkfdCldyF34baGzXFythxf22KUNKbvrizNbYyXGryLuqzs9m5GEFlDUQefZavfskKTfLEDz9ELGcqJfuhdHRO/gfVjXTnGeWA3mQgVCCUV074PADSSPcBLgG9LML7VawtLrCakkdXCUYCbrtGITjd28lyPZuOABm989R1y/RqYb5u6shFY+s/iBK2Mf+f7vng2DjydU,iv:WC11PUN5aiu2sPQGJFD5B1ZWWS3ivvIXlbqA67rhmjA=,tag:7pDKQhjQTGPo5LOlIvDSPQ==,type:str] +sops: + age: + - recipient: age1enkp0mlswl30s4h7z4qvyha4cmc2n2exs0v97276q5mx0jc86ggs7g2dyq + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBzSlJ1ak55T0I1QWFwaE5p + T2hBWk0vZnJpcTYxUDYrMkxjaHlEQkE4Z0VJCkdLdlFLRVZxLzV0NkQvenVVd1Fj + ZUZhdnZ4ajJPZlJHeWE4YkhBUTg0RUUKLS0tIEh2emtQZ2QyUmY4TE14UWI5MlFQ + QXJ3NDVQeDVJd21uS3BuSU9qYlhyTmcKkzYPbErSC6wHKmu99a2/3TTvGeoXNBs0 + yZeF6s+yy0CzUZaEXvMwGnXGBlngXLoFik0KIGD/TLiMR7DADsVu3Q== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-05-18T18:09:07Z" + mac: ENC[AES256_GCM,data:BUJ/cI4aMYmnWt1KWaGpkxue9xP8USCuGyir/PCoZipGMHDlWIUKFNxC4BlOaH5qYIkXucpRoAsCHFBvHdSk92QSuL2H+N8cfMvSZ6PH6r8yGilj/18WUvbrrDwh/G5rNMZ8ykUkZbeKbSBcnDMGHeH1x8cY3We06j4jJzReIdU=,iv:WYnOSA/SEI/fMD7EWJHycQjSsC8Rw9L1WVST/DGxiF4=,tag:i88Kid9GVXwB4K49ulva3w==,type:str] + pgp: + - created_at: "2026-05-05T14:16:22Z" + enc: |- + -----BEGIN PGP MESSAGE----- + + hF4DfdBfTP3jZzQSAQdAUBfCFV0nwrMVcwba2jkW+9lpbbSnoM91ZxOvDfrgfiMw + QCEWBYqoxbLUM8k3+qHhbj13hoypKdKZ/IU0cDUBAVXFMzO0vzkw0pCSL4ByT6e/ + 0l4BRlZlrVcvRvzgTzfJTowWTAMLGW3qD9FNTj6c9Tuupoe5csSLkBuwjlLL++pW + OvpCn824C33LksyV6GLT+0BNIHASBp5XnasJIABj7Mofuu+Wz4FvFDq7VdNoL/cq + =cpXM + -----END PGP MESSAGE----- + fp: BFE6386C8D66BCD4DAE14FC895F0FE7CD7E6A022 + unencrypted_suffix: _unencrypted + version: 3.12.1
diff --git a/secrets/sorrah.yaml b/secrets/sorrah.yaml @@ -0,0 +1,67 @@ +acmeTSIGKey: ENC[AES256_GCM,data:R3FFMuJBprWEqzVoUcK13NOW+InN86bEc0vjCeYKA5tea258Tj/EkNDtBnep19gf/XK+reZWVkvbgUUK3uxNng==,iv:YkaLjlAMNbjmrdD2TaHv+3Wppg4k2vSM+cbc0Y7uXls=,tag:nYky0Y8y7+cN4fpqJhH4CA==,type:str] +wgPrivateKey: ENC[AES256_GCM,data:Iy9dmlJWbELbHHVZ9BSup/6OnoQvp6puHAA9qK5uyHGhbqbI/0Dj2yian/k=,iv:vduj39UMLkTa6Etzdmj/wPehSR/QQhjtqdrtBzh4uq4=,tag:UrYYNhiP3sR2hZ3G605tTA==,type:str] +dn42: + wgPrivateKey: ENC[AES256_GCM,data:oviJ/7+nI+J7/WzXw0wO+ptxYl25uwLX4n+oWppXSLdHaUszaVlzq50tKj4=,iv:zxe8b49pRWNT9E0aTa2EgZaCoytPmA+pFDztuKn7nsY=,tag:RBAYBvT5oe55aSyMi+o63Q==,type:str] + peerings: + tech9: + wgPrivateKey: ENC[AES256_GCM,data:cUmMmVEC8USOc8BLSBfplx9UKATaRifmBJ+Df10f8O91h+XhXoGoxOD6PeE=,iv:g0m5TNPb6jqztxTFQ13GShDTO8Pnt08y7LwGEnHYqyo=,tag:Mk5VmjLCWkmU0tcP5cMlLw==,type:str] + kioubit: + wgPrivateKey: ENC[AES256_GCM,data:HyMZd3itnBA+voVRYeR9DDO+Unbr9E9QtGgam1y35sBlnZJQqo0ncydipmI=,iv:6V/nd3IWnCq40XZrf8hibzgPu8rYiAPivfbhjWmdrDo=,tag:p2eVNtQnrPzosqodtBObdA==,type:str] + pleiades: + wgPrivateKey: ENC[AES256_GCM,data:iKKi+kuUbMLOohMUKXd5T0ZF5Sy3qpc7WKarF+PTFEz3Cqg1SxR7TV8nLGs=,iv:wpKdAqE39a9kDUmSuFqXQ3OPvtw+2D+lLiJ2w63ngkE=,tag:ERAG9oKThCURk/gYceTRmA==,type:str] + echonet: + wgPrivateKey: ENC[AES256_GCM,data:bMdSdYWmpW1wOLqldpJ9UGaVfEdsgFI6pLXoNhAcxg5Kz1ET2NumuIDHrn0=,iv:lt9ROX/Nr2jZld4kKtG7Rb724jAcD9MyWYssT7t0DqU=,tag:l+ARToZrKuXv3H2CEDpsFg==,type:str] + tbspace: + wgPrivateKey: ENC[AES256_GCM,data:1Ykxn6gupvcAWQ+GqR6NdDjxZpEFTI34gIXd4l9+iSAgvkWNzMbpcWgi6Uc=,iv:5NY+jGcWW3KmI3VO+T9jxtIfAsfhLLxXYfCMZYQYW/U=,tag:RVzFJJWP+/DZXyW1qIIBQw==,type:str] + antibldg: + wgPrivateKey: ENC[AES256_GCM,data:LVy4QM7WP7+u1cYeBxuX+bwO80R/NZK/gQIApjVGjNARYcndd9ZzW6Xlha8=,iv:b3R3iw5fa/+PD8n+9J1H+mfGtLr6Rx8W0fAtzyzkECA=,tag:quqcr9BErwb6ViYjDcJUcA==,type:str] + dahlabandon: + wgPrivateKey: ENC[AES256_GCM,data:kg69gs1nX/KE5LEYIij8C+VE/INe0l8yNIG7uT1ruAkIVbcU+oE5tyaEO/k=,iv:XU1YcY5COxduz1Sj70hXjMfhWiub+0fSB5DI0aw5ze0=,tag:sGBE5X3dkwusvUBdfQR3rQ==,type:str] + pentane: + wgPrivateKey: ENC[AES256_GCM,data:XDT34TWLaBK7RrYkpdgccblWIWUSJ+6cMlyQ5uKjS0nKW9RGojjEpZU0MTw=,iv:DEdZS/WN5HwSxT33RcA14PhgtEZKS5s5QMNb6MgXIvU=,tag:J1fgIWyzbLyTdJ2xTmTYDA==,type:str] + e1mo: + wgPrivateKey: ENC[AES256_GCM,data:et7YXqEzxH6fg2SmVue4c9P++K1rvHv+tnUWT7STt5y9Vko8WKsnlF+FrEg=,iv:WeZ//v0X3cZ5K35DBCL54+ZFsQCs6BVkVlk/WIsERuo=,tag:iirs6o4hpyJF0zCfraV0OQ==,type:str] + clerie: + wgPrivateKey: ENC[AES256_GCM,data:6oxsE52y1cubCM6hebxGHM7Vq0yRKOpOvI/IAFg8j+O6+HQvH8OiMsQ95y4=,iv:O9Ua7lkYOVHO5KspnreSr1Sp4CGvODKMu8sYsrXFlWg=,tag:pK847w4CuwZIXwyLcu/L1g==,type:str] + etwas: + wgPrivateKey: ENC[AES256_GCM,data:HU/IET8vL2irRKNapdDIXPvTroLMSScn/mv4p8coQZZt/P1Ed88EOTs5xOI=,iv:7K3cLwc0gjeNhwmGS+gO7k14fN5xZ6tiiG/2bFxzcYE=,tag:42eOfWjkUzWzFQpaEfpKww==,type:str] + pilz: + wgPrivateKey: ENC[AES256_GCM,data:JRe0SZ+sStPoseuOUN5gSeMOyCJMA+455i/T+OsZUCe6dzujdZXzbV2ub7U=,iv:g78F4vDFeOxdEVckL2et/VjTTiHp60VDcY3Gz4j+VX8=,tag:7by6mA6Qo32sFj8GRTUaAA==,type:str] + c4tg1rl5: + wgPrivateKey: ENC[AES256_GCM,data:+ZI8DOp4TZE04W03EwOZDkv7zyutg9kRbS6KoQOLd4kbXNGOyU+1q13eYPU=,iv:ccseejEvOIVDNJDYJy34ya2sPegUTMhc343Cntq1oWA=,tag:8UsTBwduJiRYcBJxE6xtjA==,type:str] + wgPresharedKey: ENC[AES256_GCM,data:vz3GpI6RYim9WcqwDFmXHqMu9uM+KVWdFt9wT+2xZwHcZAlnsblQXIqiuQg=,iv:NZKR7NRQZSh7NE5RVKDs9wukPeeYUSRhCeYrRAbeK8s=,tag:3ETFdVP9DXqbgn+DifA+YQ==,type:str] + lgcl: + wgPrivateKey: ENC[AES256_GCM,data:g7VCZRpKMx2uhYYs7eItJvEVIfsm4kbFbC3OVkejmF1/IqW5Qzo1Ps+jnH4=,iv:NUZwrIV0W2nUjh7y0tOYodPIQpkr5LqrHVNvZ0lf8RM=,tag:FM/GQB6YJTeslMCt3xA3Hw==,type:str] + mira: + wgPrivateKey: ENC[AES256_GCM,data:xJBiuQI5F1zeMLbN2vOhD7iFC6KmWoc9iAk44IcaAgR8HL39Q2FZsgSGKCs=,iv:vfCv0U8tabDPwi9dnjrtkn3hWtjYslcIndVIhy6/qWs=,tag:yFiUM5P2cI65GAo2hDgWog==,type:str] + lfm: + wgPrivateKey: ENC[AES256_GCM,data:lTMtonR4XxE+xb2jlMvmb/MKnAnLWCW3xsQf4JsuxNkvUzyQ2aorWLLwxR0=,iv:13cFXvCsuznqCO7jTIMx77hTEPoeS5sQK7zUMVFN3aI=,tag:VnCcN0iBGkUEPmERvanoLg==,type:str] + ember: + wgPrivateKey: ENC[AES256_GCM,data:xKyTx57BobODn6l0QI2zx9gQSrIMFb9KftMdgFaKXn/sWQ7zdvNcmfAyeA4=,iv:WhMRcm70By4C+2aa1vl/wPjcZmr66wEsPArIQZmjZyI=,tag:fLAfgl2j1pXvdgciYefLyA==,type:str] +sops: + age: + - recipient: age102glvtt2nwx4ymgjh85449exlhmzk56cayy9hkkpl82u6wm64qzqjar7ju + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBROXlVTkpCRTFOTnp6OG9G + VlFKaEUrMm1ydVdSR0dJVVZqUjVwc2pmRlc0CnA4Z2oyeE9mL25oRjVxbEtEOWFt + ZTJyOExPYnJTcmhKSjFmVXR5OUpZSEEKLS0tIHVybEx5TXVrZ3dqNHhDdWpqeEhK + N0NEdXg4Z0pKOG00dzZCVUVZN2Ewdm8K7xq9sLPppekHYkegD3jVMyLr5FjjKRuJ + Uk66Dd1gcbeON7wa8yfNINXBWO9WaJVht0hybi+hfNWDPm3KtKsqYg== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-04-10T18:35:07Z" + mac: ENC[AES256_GCM,data:kSH+ZwdccC3BZIamHbBSQ3dQDmvxBwuuMO2cjRUyi1i3jd34YJHnjYXfsz4BVCo4n3n7dYwklIfXUMtZvFDqj31L5tjwsSYF6QdATpzHWWIISy8dTXb7PeFXWTFR9KZIxvKGGxCmWMUruGNeg54hhENza/1Vfz7OFY7mqpjMTDY=,iv:gbS8fy5FNl2JBW7pAfeYARyzZK9bm6PAFFc5IrVHjoc=,tag:J6ISYl6Eg9yuv2hiqImFGw==,type:str] + pgp: + - created_at: "2026-02-03T20:10:24Z" + enc: |- + -----BEGIN PGP MESSAGE----- + + hF4DfdBfTP3jZzQSAQdAhJqdMbplMsk61HzXwYbIQHJdgZEyiYaaSOtorPcW9X0w + ZaeYVHuBPNwL/RK7AY7jQxOg2sUXJsBgP/gpj77GvO6A3JV0EnKPayDWIVKcKoYO + 0l4BzUzUTO1NSC2yMZUn8DM+1u7qHcCqPeSpsCPM0JJBPpsGT0QEPtXdPBqv4l12 + db8QP7wFrRDs3MGXwWHUM6eaw9zSCC/KLXTJ7K8jZmfdvm9enmFq1XQ+c9uvPI5X + =AzuY + -----END PGP MESSAGE----- + fp: BFE6386C8D66BCD4DAE14FC895F0FE7CD7E6A022 + unencrypted_suffix: _unencrypted + version: 3.11.0
diff --git a/secrets/zaphyra/floractl.yaml b/secrets/zaphyra/floractl.yaml @@ -0,0 +1,28 @@ +config: ENC[AES256_GCM,data:Nx+q69VQ8OdYGmii78D4sGF2U+fUUTJNxyU/fZXBWYG1QEnlrqK1ACAKVVbiigvI3uAVi88BeJFHH2mzsZ2Adb1DapyFdmU2e02dODoLbN1nNU6kSHBuZpNus9nCQKJ5oHL+JGkxHJIjl7Ls6GvkT8kTq9fByqykBGnwhwhH2cei4+1biytQugyR63hzJOk+rkeUbsohZGtraP+9H+LutarKaD5ymq+bn/s8DDR4AR66XjkS5UOVJ6FN4vPKjJmyXhTNuTCbxYM6zPYhXdhApeoY7+fAqqrUcJLc5qCBvBTrhDe3H5B84fcT8jgFSURLaOErqZiXE6LHASGoBKcHfdO9Pkgz+jCi0RQjS3OVaonMQC4WiXIu3yxEO1lvdKY23NLS6Pnc3bnDKrtflgPjaa/Y8e0qAd8nkj/ywIpWpUXOTplt876Ubi8nagQEAr4NvJZyTT0DI26PlflLTRsFdeXTPUcEuELGN0LKf2nFu3pyRgFum+f9dYESAnyusEBojDhbavA6+uLMa7QtX1bzcAqmda9AuWyuOM2HhaXzU4xZ7DEBZaJMLgcRUfjQUfoy1y6HIDR38k19+1gs1ePDwJhuOUjZ5QsuIUVyh3FRTM5vpaVZFnalsN7r3ZRG3hGWgorUxZ9HAcre9ekaoXRta3DJCLFgNQxiVWUM0XOMV+Q9uBsDkH+cDTnx3Jr0P/lIK0V1AdAMzRtCGw5Y3V6RqU16+kDZHdDZTahmZmsan6EiJ0tlPDB/JLfpCY8wU0GYfxushEEowxrZT5/3fWV7NW4y9PpxaiLxiiaGK7z6j4iFxfM/NmkiGsGiYj2j2tXH6ilcK3hCOqzsQvVhAHQPVjGYGAiXPuHFcdfQZutWyHNsuEpCglvuT1W06x04O79hRa3SzIEc/LidrP7a4uvObvqASE21WfxcwHuHmN5hpRk9Pl+XoYUWZyQq37kPrvDxY97HZpzz+xkly8kdAOXBTnvNd4jXxUFhfEUr0iYAVmOjDkXFpdqIJhMH0uYkYW4x24V0+wZCDaTb9Wbx7L2y8nx9GDb+uWlghcQ4vXtgIg0jaxxovoomTpHsBh9JoA7e6RkZxUTpO3aIncsw842hbUiX2CWdf3FC+HbcCIl0PKsuktb7jKc3Z16ZECmCqITICpjWOyNrOV4m5mpmVnvWwdstznRgK2cQGWLmGhSVrtlz7MKP8vNFBZL1rU3H0wi95MI7X9eeD6tH/M7/66ARA3eho9O9l34rSksyUy6WX7QT6e5Rldp+Fz1M9nTZxxtvMIW4DFAX8Gp0ltVa4xIw43dwM/UClkDq2/bxmeqLAOg0Rj0eh8Nc5zK8t4yTJd2+3Aqez8ormAkUDcxm0rcYHR47cDvKRGbR9XxZyIp5tExpfU2laQgz+9AsHRnry8S8m9Pnya5Me05tA1xhjdwhpTGLpzYgoZxzYvBLN6Thy8GjVzHU3eGA5DSp1TkGtI8c+5LC5F8Q8yvhmjOvqCFXepuJqVfLYOww7OoODjnyUPsPWVZfzGKLJN/xS/dowWyUY7/uaz32dDqmJ9AZ5IUn8b+xFmXLlk0Ri4jGCunP2dvRQJFTQUTP6Kq/mn/vI5dpsXaIOMRKgoy2UDHK0vJ8cIl+Q4MjzgT3ZronL9Z6koruVfUmMzIfnHOmS3zO7FBBF33iL0VoNIlazgDDLWvfIMXNAlpIroNrYaeipt1TBrqq5ynCo8+/yhqANq1k+W7mK7P87qcggcynHaFNREt38fvjMTaCVLRg3VDmKKStECFpLEl5BGBkEAVQZci6z/9rtnek163tX3Nl9tLVixOrSgKklofjJzskK7Jd4X4U8Qk/3msBAUs0y1MRxc3xn2vgggAqwcss3FeozHsXGpxfdPOn7AeaQPe4tYFz/RQpD4PR7nMCxIOqw1wJG6+Vanbk2+8/7nMjosCXj2JmxdjUnatJQQ/Nd/LH0of23IyneBcc0nDm0EYbc1hV3266nM/eZAX8duIRsjd6h4SEzDeFlMFWiSXnt7oTHX7PRNFtqk1c5GoZIFlSlTKpn6Ya8nmoM9FjX//Dx/EsVMAR0e1DgGwgzEuzGXeoj/0PBOq6MolJH2RO2kBOtwrk7CO9uP3F/bL3jofdeyLBMc0Kzc5jgJCwXVmPQlNsBV6cSi+IiTh4ZH+L7j0VJZjCvLU11oBQUTPjjpAPYLf2dMMkCkQifVxdjxYCHgvM7xgEOvOZUHcySuEwGJ9lEy1aXH6QCR9mAkirJAhHrwuJiPavcKmBQKtD/B4y95YEPKvHClrAFKtx+ES/weFLhi2IZDLwBvWZ2w6vMy767x5vuUOtHHXWyIz15R4Nwgf7FzYG1WEXGusLCbBHR/YtOpw4YQ/lC9jBQJnno66uGqT0kNVXH2LpBZxXGR06zbyuXbvsaMlxcjvAT4rshXVx5jtJ9onHrYCJOm4cU8eG4S3D/r4+oSV1jEbWm9cl8zdD3N7ORri7q9zEWtxEJSwEKw5I9JNNHzzpMPNZZWtbL90Y+LZ8dHi7DwvyojMI0O1cTgiIDChofq6/hDolCC6pkPYPevaUIpMtaxPeVejuBrmOvhC84LNe0O9vyZIHeQ6EPqAej50IdIA8fyp+j+BL3/CSIM3u2RVY5mTwO5EF1mdHWhb5q1/mnzupkKwa4sRWK/uZZ/lHTnjKfHWrgbGfpBhLPwC20BuPATmI289LoKp5HCRpCshgaf71euOQ+Wl+aWHqlls0Y2mWzz7YGxcDPOhM7Y74DIRR/BJ3owHZ1/vW1RCar6VIdqJkg9jhUkDqduCUdXx1vHzgXYUg97kt1BqvIhvbY2BQh+HnznJ+YHMTpSbwuxgKT4wTlrJ4nOz7jI2JrboFSD6sWhb+it6eEN1btB8UNG7FQawQo1SY6T/8W5WVlGdy0PJcUbKOST2kXw4aZT+76dZzM1z4w99iogKJ2xsx0uN+kPsoDRrHBQ0laCiDNw/uDwunXchYvzXh24ok+msKDQ7Xc8iqHjnlmKcVHrIestRbGreqf0mFhUyYkp0/Pcqo+M9/bBxT4jcP+79MLTiuzNGHLn0hAR0//OspCh0fSwgermG+jMeud3/u5VWAGw+BgE/WJM4F4KG8gI2JHtxYugJvdjUzkBcKwK7JSUwbywvAyQEMbMq50lTEIz8RcaUkSRXTEEc5VPoI1IoKsOcoAxvdHUVdKx8Eg4062yc1OqILRBCEVSLqMIquAsl5Qo4n7uLSi74apOUQpwoPrx+HNxNSz2KxTX7Qu+/eA0RaVG8hpI0N5T5XShAeqaaIDrzDTqbnNoH+mg3NamR1kk9oWJTn8tkz3Ku5dBGYExSP9r4lRI8OVCTQMit+5WIgSfIugzsqPLBfxm21Et1d1Ugn2T+sXLMAP6RTfOvU89D4JewFx86LtSgxSbVWTkWjKe8HC0Qq9Pmwsoi9TCiVBBtNj57g75lHqomNtDWbObAXmNI9LfiHlncSC6vEC7KeD2tFui9lk9S+1tYAWlhmcP6WvPWeOOu2/qIlutDrZ2XTuw4SqIZxJi9Ylsw5DKlCWRBtuoxXULulv/zhh64J77Nt5k+Pw4Sg+lzbOj16/GcZF24/vfReyx6MlcbWrreBaTKC+1qzN9Bzy1/mkIk/f6Pax/aovsxFz6KuT5ZlxVqNHI2ma4FTe8ePri0XyXgbxSnyWrIuFhp/CKVIKCGpsIe2+Q6taI96rNymHgVphAQsmAmZrf8fyTmhkc51TrahXWeUxKGjUvAYb/I3amT+QVpxLzWKm14RQ9GBY9UTSHYz4aWmaoM1VjqUBYfLxiXE1rC/eKBbvYTDxZsBJMr8ASXqmOf+t4+C9PgxqFln4LfltHDtQYSp68yvQAnjl+lGkOJ9KS/p0YmlrpF2uZYxwz+DlXXQO6AOq3+3R1QRy2RoWLILL3hGhQAIncpYJ5NUySDDcbMzm81EY5C0T7bGigZ1ysrgAqa6Q5lJGRQMpJBcdGMbANKjdM8O8VKV3JifNOSWgfRtYu87ry77oNCgIsUFnAWDLvPDrzCTCFJRuW1rZlY8xrTAYtR3W58YfEnxON7yZOZQQbL18IoZyqAX1sdyTeVsIljTykUVONW2FKPC3BsmaHKYJw4NoTcC8mv8F9Xw4bpDVE4Em/Nf/Q72PyH2t3pavA9VajJ+nAer9iiXe//9AhK/vl6sb4marEW1DmfbIG5Qdf90XXT/uxuUYGmHHUFiGBUe/FKJYEQM+YceiLTpmh4LKzr0mFO1GcYpc4iN91y4nlLNFQoBYBe/DDh2bgBia8uvNpPh7cj45JEDpcbz7X94x2U8WilgLugZ3QlTGKlepd4tDSjk9RD9yagbmhYkWbGVdhobRf7z6zMco2jDXA2fYJxBW8Pf+LsTwwlni114m4s0QLHH0bbcUt35lPnb5LxAYOg/qP0r1mRNPuy9zIh+cHXdfIeW00tqso8fwNPc8RY2theMew1NLVbCWjeZU9w5f6zt+kjFAA2yz27jG2SENjU8faygWEXPM/rwGlJySsZQBAsvGeUwagyNWkgippm2k5+82n0BOMQrZ1ma4Um2FMl2E3CqPXHOhmB3QbNRdcZDgHSwmwRyXO86+mXvDzDptSdrPc1ulm3EvM5ydcxJamthsKqOYr8px+hY0J6hpQDSBACoVyYuZ7EqYR6sbk7txVzHDpj70G0iK4oTMPx3r2FljwyRopQmiS6iadAAMRMTMPlxwKJ23/GqSeC5tsbb8YrQpjbc6D+P0//tM35NCFWSoL5MxGuFDXxsamYmQbAv7TMAGiThJWC3B4jJsrSb8yTbS4Hkr/JAwGljFsRjfhuCwfr0MyVjkl99/OLTi8J3z5s6DUlW6a1KzWQmOPcN5y1Lw6lZXQIv/UJK3gagfrXT3WSkqajiOA6cujY1MQVJG0Hx4yZG5G5LnVfgwpeCm3fpiqjT+Vj5grf3sNr4FNsWmDqCX73IdRiLUuXQup9SsiiSPSkdChzHXzfjVb9BVyrfYMfGAXl7vS9RyuV4hd/mFEAruxqnpCCPguzIdEdXy0/eZX2HpbpVmNte7JrTUcc5u+oxVKfASwX9vvLd+NNRu2GFqUwyv5x1FkQtzr44otssKBKsGQ/4qUA363Ek+ZrbI6oIPJPZJqIiYa/kl5cAh4LgXK3pKmz4QTFv7LddtNdG0Z+UgVE8m4H89xrbb0Hp3rZcMiR38dniilEKt1zEJxInceC8RaGUBpMSwlq9gACxZ/m9A61Pw7i/qUkDRkzeNeZOEUyTyCwdlILJof6kvbzxmTrh/2WqC2Wi4Dg3VCL/Y5dLk7WS+fkeK6fBeH0Z7S6uf34h1KLa8f0Kul1uAQK4U4nPbF8Q4pYXhxWylYyvB0DQjItsQxpLqP/A0rltTWVuc7kAcQayL+McuXIWFWIAVA8BXmosittp8oLa7ZU/FR4WOudAFwpK9YUOgLC8vg2qnpy9T2xaN6NBVuKz/DHbIvmzeax0xtt2l8TlNPAX3W0OUOtRjxMCJ++MU8JKkKsbiTD1CpYgAwvFb/0NlDzP6KcuOf1BLD9ntqquK/DJlcakP1ez7YWdN2QTkrTz07PWg5zonusGCnNq/KDVKAw2OK08SYHDvr0TDFRVpQUYhS9QAIEr+R85gWjArbaI6lfQN8r+yCDiQSmPxa2ooakTshvujjdsn4DDMYjbjsU3k0kfdIOQ2pOJgum6krWISR18u4zzux6Wc+9DbbcGaCs3hwg3D1pw70gzl+VAytscOYgW0XcAXY/PmkBz1X++h8YujKn8Y2I0HTn4YlssV7QzmZ0mFx/fweNVdgssh1pg9OaEfdoWpqowLa5pumEv1z6oI7Uvws5et/DnrY+qbl3xTfb0yJUoNc12jSNhy9+4K+EgcpttlD/E09UVlAVKcSckKbYxDDXBMQr9yYgG6W5AxKv3SlrrJIejYijPZJPEH7FA4K0I/zOxsPODkSVurCGRHB7WtppY87K1qp0b0EUrnnNA3Lk3edzZR3TlOgDq9UCnZoePUJOsP+Kw88gNxAaK+1Hk7GTLEsR+ATIbQG2x7T/oe/zzKxV7MRA8fPJx7/8NdvkC3uEByDuX6eNYIiYcpYZ8EKRbsfrvhwANnvRS+Vtg6+OOHkJqdQ/0/akNPlNpw+7ICmLNmmVpJqPX,iv:cR48sW0FzGfU/nNyuZ6eREbr+/viIkCoXZvV8gcArgc=,tag:MrEtauculvnuW0u5tLR3rw==,type:str] +sops: + age: + - recipient: age1wztgmu7rm4yvdr0x8xucwfmnf0sruafwjx2ttl9cvg54epn0qysqqnr5n3 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVaFJub09Kak53SDhsQXlU + akg3eGJZZlVDeGcyWG1RSXpMN09sY2IxdVQ4Cm1rejJEUEdtcHFwRldlTUxRMUMw + NmFVOFpTTnBsWVRLQSt1amIvZHVzcUEKLS0tIENDOHZYdzZlSE1sR0ozVjliNytx + QW03MktTTituUko2MU5zQlN4dWptc0EKjYAzjWpS3lxsjzBs826SUvzUhmHNuBeP + fwZ7Cagz2decze2sVWN/r283tvX1n1sALXqvrX8LzQqVwCVgQsXeWA== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-05-20T09:28:22Z" + mac: ENC[AES256_GCM,data:tKxBymJbW/cqua2taY6pct4ndHDjzBAIn5ptc3YOlykyr606VTYPStmRyBKd+hx+98eq9v0fBJJxm/HVA+6VoibLTd7dazZWk4giWfs9kK2tQ5Y+v3GM5k+FGF4lqkcxXl/S8xLaBvTd0p4S+Lw480HaiszNdPrCS5Uxw8mIw1U=,iv:4QQQdear0lCHEutdhplF1LyHPBnjJeL6F6ynRU9Jxes=,tag:f//PisfFeFda6DIu8Fr59Q==,type:str] + pgp: + - created_at: "2026-05-20T09:27:49Z" + enc: |- + -----BEGIN PGP MESSAGE----- + + hF4DfdBfTP3jZzQSAQdAdGYhFuC+8CgX6JRcJwDir4pEablD1O1H++teDzhvVgcw + +JhIXSeBfWJCSFoRxI8VlmpC1snWdc91Pgg6S5db1iFP4yY+svNE6ve4/K5tk4a5 + 0l4BKWXSe79bqnWMlTGU7/bQRUvtPlCtVrqcLyLBSccBaHJnoa8BzbVTuquY32DG + TUqq2nv/lYhEDtJT0tMSEqSI2CcgFuCq6nseIhKAzlurYdNPOWKIlB/uZR4QPJOV + =LRS9 + -----END PGP MESSAGE----- + fp: BFE6386C8D66BCD4DAE14FC895F0FE7CD7E6A022 + unencrypted_suffix: _unencrypted + version: 3.12.1
diff --git a/secrets/zaphyra/sieve.yaml b/secrets/zaphyra/sieve.yaml @@ -0,0 +1,28 @@ +katja@zaphyra.eu: ENC[AES256_GCM,data:Dz3h+Cck4H0rZWd58CPy6oj2qt/hkfVxnH4TUV7BDphfSpSwCJXsbQOOexONHot8heCjNqXb/b6f0cUZgvnP9EmKa0fQBE8e4wHY5WRcLKdD/MBJ0jgUcGcw7en6vBptGxU4rdfAC4SI1bvivXn4hg1ST+we+LDzWs9lch7sVdVyX/FY0LtILUPUkls3zbr3eEVMPi6bY7xdWVLWzv5EguFZdPnUGFYpRYM7oZ29VClfFYoFuabE+1P2l0RFAIaKw046W+tvzQ+9eTluGBtCuj895iYDxHEQNUIyo8aj3QYDdsNDli4MnhFJrfMqmaFEzlTcWXZ73elp/1eF1b3EtfWx8dXLL22ThquNkifT03EqTtoekB1vXka8Aokezz7b99iZVPBkKUI30+wn4kkgj2kbgmlQ220Fr9xBUjud3g1lzXG26QIPb0KkFjLw5XgYX2bQ9FjFlllXcCjfOLYxvUc+/jbwL/wuqalxBic3chcnIXnLuDscDmY7UMctRjZjkcSQ3hE82BtLiDUbAejQOKwWeLqhuC7DcjYekhwdJs6ZmTFw5gaxayBl6c5QB6pa4DhHHRD+FhvsxyVSNyy2XT7xIQh/nBOG0gaE8mkKpQc4NVIQCUgPl+gNxB1zA3aAYRglrmwSz0pQJy6cQZ14gmEyDUZzlBXHLLmuigleuFwWWeG3ffnx4BQRV7bEcuGVbULeahwRwQGQOENdIw7qvTUObf6lzE/zm5z+68xAvgYFwQKXXl2+YHKRRa9xVoxawGkStYzHmA/fiGk+QiQekxX7zcBYqzlAih0vVvQRGUls9yjuuTpQzy/r7pCBJEVWqSvOZnYCZHZdLZBDYRVmf877yGWond95Z5jivzzOMfbiJCu8aIRLB67pCLImV9y5IoXq4s3/DeComSBgD2LpT86CoIqdmPEFJjoGw8hfKxb1j0Z0cpxI2P+TEqOap5rWDyF3kFHQilo69tmzayVhymA8kAGl+lna88j3W8/S5teq3h8DY5rixBA/v6D57jRNUzvnkCtoMBNBNDdatNwzPczr9bg0IRzsiwqP37e0DDUN4Y6btKvQv8jWbyw0mFGbkqRD8madZWr2D0gJm88RBYXj00ohjc+WktsczCV3KCvv4DXbNW9WkgymGOHqDhediRjktT2C4fGaNNB0ezIWGRaYB9HSciAwIRwd/ia9zYSKGcO7jENYvSLm2T4P9/1U7Gr7fO5LtdgK32UcYJ7Qx1nXK1tbZLfMHhjhuD4zsp7v158kVJfgKM07/6XZT2lQB5ozJ4C9oz2YEjIsG+AZJM/S+vUJ4JQa/1t6tlu5vcTAIn8Wi8G50fUN49jodWS3M/Qhvtyo2iheTsSCmldiWc+WCnD9J24XNnxvCdm7sdtqEGZ84vKbn4ASVRf0n8UBj4GzH3UaCeTB22VmU7oOcQTwUom4iU0oyj2DAL1tpzhuKoVcd57ZzVcqwVfDsDjfYiWiLWRww67fkzTOrNL6RfPUD82ciuqP4laTqgcYdL3WRVKZPs50lA8ind+7F9XGQrZBF2iS+L7yOlZ4WQIViWxhNwmOoU8FFlHO404PZxM5FfXvGwD1eQ1WhOSTWEvQJIWB9147vcF+JYGiBHnAr7MTtBTF7W6erVgelZN5pT622H6ETCUpKVB46jRJhGSxeYqKiCic60pLNgRCcT6K7qpKMrzhnoNMr155XYHIcbo1v6ma1tft+gTGauK8h6A1UMlWiKkRaDkh424wIcx96tqerbELOLYheTdpM6BkAQ9EcSd7NcSNXRJiyAtyz2ftLz5bqdhZUCJt2TE4gR7DaqK5Os6OEd5ke2BzsLpRoKJuEM+VFhpuAW2xA34uD6kD/XW7jsT9Gm8rLxzrQlxsfDWAuHqQSkpTf+7jvJs7Ycrjq+Vxh37CTg0Ug/lquYnzQjhJkDX5+RAo95RJuxLv4y5gTlnpQrbtekEAHOiwOo4FriIvbDAESWm0codoCXAhAiP/5dEXYwXtQmrnl6sx8Ea7jMvhN6YpCu7WMaApSmqGqHuyVLcjc1izmc7JeuAtPWzUUNqh5OsWgh3FDh1YfDLTEsaPWDh0cNVMBSl+HgtVhnsfLoydKbd0fiIXEr2Lm7peyYze+F3hOKd5z1DlAYAhm1DojsRu4R1YSvE5IyeYE3BugTDaPX9qysdLI+TtCtYghwkhc4YH8cI/vR/xt/RUF+4W3y6y562PjNLMcgGAtUDzVc/mqmPQFuVlsZ1C1bkw2E31OKoCKcDutyoLbcOcKq6oFToL1+VGqRbqy8Jc6WTSj+hJP72uOhCXM44G0Ph90kl6KkJoi49QhVYaqHcDce0EU9La5WjagP0uJPpbpLk63HE5yg1H4GLhTXsJRZv9jDEEtv0CmSiNIfgs/Sr7WCEve85udOCQYUgsagd4/m4mVw77j4LwgO4uQINZ6RB06pULW4tJbOhPZCG9AgckseTGuYEu1JlI7n6sOcddDhvTeIX4nmGIuyt2AcAP/KEd2ImTH+ujEfCnnlTbJPU2V5/04utKzTeGkaoeZY0VCaUeIoS8kpA1pN0LOk4uf9MxutbL0yuoo3vF75e5pLbA0nxH2uyNLFTcltbjsmZR794ZGzosdUnAoW2atdDitrGPo4ndNW6vIdeIiky6CV8uDJt1zwcdG/PE+YSh3Ytg0v5UI2G4XfuFD4GYeaXvylUOB4v6Jft+eYx64h0MH5Stue+fY5rak08i7GhtSKrN/9K9Tx5vNZ2l+SrbKaJjWsHakDZkOTPrcl/YGKT/var4CP4t4XMnoKPvU5tGxR6wccDyklhZLF99MNV52iJh1PBI0AL29YP1CxiJXztfmFW3S00o0kBpWMd85kVEH6k8YDxsCc1qedhQIyAXLO2AcX6ynV3Jeig1oqZxck0sOO8zwLStPLUZVBS6TMEBChR+60qTXFVr6DvUts6+rCIvbz7P/tFZFMVt+4Hxe1CqCjnhME+FxIH7PmTibfeeatmNNpNQRaM1FkVd5OE12WYOIDkUgqIk4WauAy194QbJ4WJ2+O8A6M4deniLFbN/JT2EEUK7tfrD0EMVGZhh7ltpJMc3uYB9HekCwZm2Ozv2A4ecsmvN+FSEpey77vrulF0Kxx9/lqkZyq2LEFKehjDe8LbtGHGU4GsqI8SrHIYL1zP8EwbvMPgsP6NMoA/XsPiu5GzoMjj2BmAME+VJqdTO9IyFsLfml0lmfXDpfIHYu4ZoJEXdD6FaFhTThiNSi4WH9GcOzDYREZdJESeK02zmc07CRun0Gllt3wX/KGAf0HHBxMVxJ+3BriKxFlYsWm/7tmWTsxnXI7W0sz8xaSrV48lMe06hez58IR8CQBpBCJiUeMNhTQ5XhlDjWOxS66BSmnevn7nHhIKdUZQk6ywyDjgqqSNeeAypOSqQTdybe9Et7rTUyeGlNvl8dAdVN6niE2e1gzpcy+iMDoXO7oi1JSgTJ1/k/9fr6iSOM1+ZkFhMqWv0dH2YuHNE80YwYb6P5RQJ9gXHTMaPBS6jh/dZ5rwJ8uRjda0yYbm2vUehoTDj7m/84JoyvRNcoYh/clYkNtSx3oFYN+5uH6xRfEE=,iv:FicnrntRYIa/LCgNuy6YkkTwDmBEaGw7XvZupWy9CJA=,tag:PDPiZt9IwpexWcVX8Qqk+g==,type:str] +sops: + age: + - recipient: age1yln5qratl82u6sqakh9gn2c0s88nzm62cw9muq5z4kzgp9wqpyzshhptq9 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPajN2bFVvMVhZa2tLTCs1 + Z3lXajhENEhUZFV5RWQrNmE1R1ptRlFwOGtNClVtZlhvNCtmSmpON2ZJUVpaWUhi + Z0psNm5BQllIWk9VYzJwbVhyQ09jL1kKLS0tIDVkaHJCNDJQdTRVK01lZ2ZrWXFy + MUNKWHRNRDczbzl5bDJyd2tQb0Nld0EKIhBC+bX3t9mxRSW6WWGLANeQQjOohfod + D7q2Aeg0PtmVQBtcpw3B9sU7WkwBtVoRq2i7ihiHvmu2acoZHoccxA== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-02-23T18:02:40Z" + mac: ENC[AES256_GCM,data:9B9Prk7mJndxj4zb3m/fzwRCp8WbWnQrw2tKnOzJ8IhGcFm2uFzTOEiAzPomOxl3hxpU9CtC0lnH04/KFL6ns9YpldJFrPYH8M5dA6pCh9Fbqcf8AggzEt8FigE0oLxPRZ0yG8evHklIKXumVD4BwpzaXduEx5P6YvzUTy6ODig=,iv:oOvScRu0qb1Pggsz/6Ic/oKthNLShagYiJ8AGAlaFz0=,tag:biplyLglpilD9NKYvao5Mw==,type:str] + pgp: + - created_at: "2026-02-23T18:02:22Z" + enc: |- + -----BEGIN PGP MESSAGE----- + + hF4DfdBfTP3jZzQSAQdA9cFc1G+OVNQ09ygBsjgoP77us0T4LN6mpV/UCWQG30gw + CIN9RxivwgAJ3iV2uRaRVWk7Ww3FBgjE6HHa/ddV5ItVDPhraZpQA3X33XueU0ch + 0lwBvexhVoQn0ySW6OFaoRavh14YYFAaDFow+dCQbjSN0OY2wjyRj4X7AKdvXwAM + 19ZtJEZWtho4ZauvuB+EkRXm4CWsyui+gBbc/fTwDxGT5YG7HUEiAYv8t4U5aw== + =OXqK + -----END PGP MESSAGE----- + fp: BFE6386C8D66BCD4DAE14FC895F0FE7CD7E6A022 + unencrypted_suffix: _unencrypted + version: 3.11.0