Compare commits

..

8 commits

Author SHA1 Message Date
Leon Schwarzäugl
a7cca50ff7
docs: improve docs verbosity 2025-12-23 03:19:36 +01:00
Leon Schwarzäugl
69b45ab0e2
feat[client]: push to attic automatically 2025-12-23 02:50:16 +01:00
Leon Schwarzäugl
905a879ab3
feat[server]: setup attic cache automatically 2025-12-23 02:49:58 +01:00
Leon Schwarzäugl
23365c0358
archive[server]: systemd-initrd systemd service 2025-12-23 01:59:51 +01:00
Leon Schwarzäugl
4a14888007
fix[server]: remote disk unlock breaking 2025-12-23 01:56:26 +01:00
Leon Schwarzäugl
3b1b048ec1
feat[server]: also proxy roundcube 2025-12-23 01:52:26 +01:00
Leon Schwarzäugl
495a2b6d70
feat: support vars in pii 2025-12-22 14:15:12 +01:00
Leon Schwarzäugl
f1fa6f8e99
feat[server]: support multiple wireguard tunnels 2025-12-22 14:14:45 +01:00
43 changed files with 2766 additions and 1477 deletions

View file

@ -46,6 +46,14 @@ creation_rules:
age: age:
- *pyramid - *pyramid
- path_regex: secrets/nginx/acme.json
key_groups:
- pgp:
- *swarsel
age:
- *twothreetunnel
- *eagleland
- path_regex: hosts/nixos/x86_64-linux/pyramid/secrets/[^/]+\.(yaml|json|env|ini|enc)$ - path_regex: hosts/nixos/x86_64-linux/pyramid/secrets/[^/]+\.(yaml|json|env|ini|enc)$
key_groups: key_groups:
- pgp: - pgp:

File diff suppressed because it is too large Load diff

View file

@ -245,10 +245,10 @@ $ssh_root_cmd "nixos-generate-config --force --no-filesystems --root /mnt"
mkdir -p "$FLAKE"/hosts/nixos/"$target_arch"/"$target_hostname" mkdir -p "$FLAKE"/hosts/nixos/"$target_arch"/"$target_hostname"
$scp_cmd root@"$target_destination":/mnt/etc/nixos/hardware-configuration.nix "${git_root}"/hosts/nixos/"$target_arch"/"$target_hostname"/hardware-configuration.nix $scp_cmd root@"$target_destination":/mnt/etc/nixos/hardware-configuration.nix "${git_root}"/hosts/nixos/"$target_arch"/"$target_hostname"/hardware-configuration.nix
# ------------------------ # ------------------------
# green "Generating hostkey for ssh initrd" green "Generating hostkey for ssh initrd"
# $ssh_root_cmd "mkdir -p $temp/etc/secrets/initrd /etc/secrets/initrd" $ssh_root_cmd "mkdir -p $temp/etc/secrets/initrd /etc/secrets/initrd"
# $ssh_root_cmd "ssh-keygen -t ed25519 -N '' -f $temp/etc/secrets/initrd/ssh_host_ed25519_key" $ssh_root_cmd "ssh-keygen -t ed25519 -N '' -f $temp/etc/secrets/initrd/ssh_host_ed25519_key"
# $ssh_root_cmd "cp $temp/etc/secrets/initrd/ssh_host_ed25519_key /etc/secrets/initrd/ssh_host_ed25519_key" $ssh_root_cmd "cp $temp/etc/secrets/initrd/ssh_host_ed25519_key /etc/secrets/initrd/ssh_host_ed25519_key"
# ------------------------ # ------------------------
green "Deploying minimal NixOS installation on $target_destination" green "Deploying minimal NixOS installation on $target_destination"

View file

@ -13,7 +13,6 @@
topology.self = { topology.self = {
icon = "devices.cloud-server"; icon = "devices.cloud-server";
}; };
swarselmodules.server.nginx = false;
swarselsystems = { swarselsystems = {
flakePath = "/root/.dotfiles"; flakePath = "/root/.dotfiles";
@ -29,9 +28,11 @@
isCloud = true; isCloud = true;
proxyHost = "twothreetunnel"; proxyHost = "twothreetunnel";
server = { server = {
wireguard = { wireguard.interfaces = {
isClient = true; wgProxy = {
serverName = "twothreetunnel"; isClient = true;
serverName = "twothreetunnel";
};
}; };
garage = { garage = {
data_dir = { data_dir = {
@ -60,7 +61,7 @@
postgresql = true; postgresql = true;
attic = true; attic = true;
garage = true; garage = true;
hydra = true; hydra = false;
dns-hostrecord = true; dns-hostrecord = true;
}; };

View file

@ -32,7 +32,6 @@
}; };
swarselmodules.server = { swarselmodules.server = {
nginx = false;
bastion = true; bastion = true;
dns-hostrecord = true; dns-hostrecord = true;
# ssh = false; # ssh = false;

View file

@ -76,9 +76,11 @@ in
isCloud = true; isCloud = true;
proxyHost = "twothreetunnel"; proxyHost = "twothreetunnel";
server = { server = {
wireguard = { wireguard.interfaces = {
isClient = true; wgProxy = {
serverName = "twothreetunnel"; isClient = true;
serverName = "twothreetunnel";
};
}; };
restic = { restic = {
bucketName = "SwarselMoonside"; bucketName = "SwarselMoonside";

View file

@ -10,8 +10,6 @@
topology.self = { topology.self = {
icon = "devices.cloud-server"; icon = "devices.cloud-server";
}; };
swarselmodules.server.nginx = false;
swarselsystems = { swarselsystems = {
flakePath = "/root/.dotfiles"; flakePath = "/root/.dotfiles";
@ -34,7 +32,6 @@
swarselmodules.server = { swarselmodules.server = {
nsd = true; nsd = true;
nginx = false;
dns-hostrecord = true; dns-hostrecord = true;
}; };
} }

View file

@ -25,15 +25,17 @@
isLinux = true; isLinux = true;
isCloud = true; isCloud = true;
server = { server = {
wireguard = { wireguard.interfaces = {
ifName = "wg"; wgProxy = {
isServer = true; # ifName = "wg";
peers = [ isServer = true;
"moonside" peers = [
"winters" "moonside"
"belchsfactory" "winters"
"eagleland" "belchsfactory"
]; "eagleland"
];
};
}; };
}; };
}; };
@ -43,8 +45,8 @@
}; };
swarselmodules.server = { swarselmodules.server = {
nginx = true; # for now nginx = true;
oauth2-proxy = true; # for now oauth2-proxy = true;
dns-hostrecord = true; dns-hostrecord = true;
wireguard = true; wireguard = true;
}; };

View file

@ -1,28 +0,0 @@
{
"swarsel.win": {
"fulldomain": "ENC[AES256_GCM,data:CVasUSMRn/KWzVRlcYfTO/RL+W5Cz2JpDj0JLAKITXrDZrl+Wsg46X8zv4hX6NLj/wAyvXQ=,iv:N3DL4JPX8vWTbllFWcpNulwtDJ57xpHrAwoUxWhTzxs=,tag:CYWoK9uT121rFXQ5h69CZA==,type:str]",
"subdomain": "ENC[AES256_GCM,data:uM457vEJa10IV4SovBDUzLLlW+mPwh1SiWr8thQisFoe6zAk,iv:Tdbd5a20Gv/thkPfsvNiAbI86JjcDs70MAfk4yCZLgs=,tag:MulJiRWPs215x0bc+1jBiA==,type:str]",
"username": "ENC[AES256_GCM,data:ePE2BEKL5uaXqzGngW9ArhwP3qwDzwULtfwUfb5Q56VGGURp,iv:/GZRbyXHorcq1PIYlhfOmUVwCg0I/N4ZraEzSrc8qmA=,tag:wM5B1U0BsRsBAJg3qNOXpA==,type:str]",
"password": "ENC[AES256_GCM,data:RGzdi8IMqm+rtiuU4RtWGQ4N/7FYBbp5Pir8/k2V1QEdM8z7SIn0FQ==,iv:ThFbY9eZuEZoyzcWV5DwtSi8ugNwM49JfRof560Qx/Y=,tag:sgMaLrPB8WgpXWPzaCwOBQ==,type:str]",
"server_url": "ENC[AES256_GCM,data:zJdXoO7ED7qeskYJ9Wu0Rdprbvj/uP+Z,iv:ce+QXocqCjNKCsZRyVt6koUyc2lsTwPNMcfQyqbktN0=,tag:bQSE4/6va+V0TORWANLdUA==,type:str]"
},
"sops": {
"age": [
{
"recipient": "age1g7atkxdlt4ymeh7v7aa2yzr2hq2qkvzrc4r49ugttm3n582ymv9qrmpk8d",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVZ0ErYjZTb2o1LzdZY2tz\nNUR0dy9DWkVyQlZBQU1WSmFja0pUN3NJSkNvClNLbTU5RFFwUkJQVUNML291eG5N\nZDlCK0JvMjVDL3lvMURMbFptQ1Z4ZWsKLS0tIFA3OEUrL2tXZGM3TFk4L2l6RUo0\nMVBZOFBYS2lablRuR0hneU02eURYQWMK1M9ng/GcFH+NEmknJ8SHOUxc8atX3p1E\nB/3+4dVWSwVdTEkG2VqQTdo/irjbTKpqZ0m5bg9zDhxZpyQ2lr2ePA==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2025-12-04T19:04:02Z",
"mac": "ENC[AES256_GCM,data:nWV/knCo/MeWTBrfq1VlV6SPEQ2i2P+le82S2So0BIxPfz8tqan0MdaIaKLFlapsT9VRJOv8ZCCXSLWeGcbEvfmEz4MP1E4iHcU/4YaO+n895D1JrjeyP1cgGisnXqe01xMXCsDY178sqxHcnDDlXp9foCem+mGjIlKGPYGu5Oo=,iv:qbavbW3MF4fx+E3aybBYaz/T/Hb63ggWml4Oe9WFz+I=,tag:05vBbBGDGRNaXJWoZn1bVw==,type:str]",
"pgp": [
{
"created_at": "2025-12-04T17:59:06Z",
"enc": "-----BEGIN PGP MESSAGE-----\n\nhQIMAwDh3VI7VctTAQ//bwwqP095CUku9qYMYLJToU9iuL7USF7UxfKQLgP7Lx3a\nilbrofOS508V2og32sZD8y8GGDCMc7HMQv8TcgIk/kq6jX5dHUYN68nVMQ8ZG0As\nW1kpo/cLZAPHoWWEG5E1INX+KSN3b/KhZgXohuVyrax3aTy0kcKeApAJlntr+gyV\nfjPjjvGxXrCXZHN6DzKZ+zqEIs18T0ByLtqLsYzTlD4FszISGCnf6Kr5jpj43BcA\ny1Hj6avzk1bQqPEFovf5JcB+O3DnkIwus+GlXihu/6gIiatbdshVKk/vDdR6TR1/\noDg2EV98uX1K+gEe1JvJdC1JrAPZkOtx4hiFcLVc5G6phdQ08hY4PZ8On4Yajkby\nj46FkPNLB4TwwSC2Ga03CadpaUK0twNGAH7oya3VXUiHqqu2rnVgUjrsZCr6yA4d\nJmumRiTHvnQjECQB5J837wXoDOivaaY0OszELM41p6UIhMTG4/SkkEvfgAI3goGN\nV5g4uBES/TGCedU5NS5EMtsjRoJSQDyvhfkzMUBDcUm8xQ3RKRtdqTZVkT75Ti6M\nmnZolAkqq3uWwmSTIXTgC7T2dnWLRVgfpj7hzZX43ucf5bXCn6QXoZscMUL9LKR5\nd3lyh66PoHghatrb0u3E1ub6XJQWkbDDkKDHRuYjU02Ai12oPd48nhyTuhnmeLCF\nAgwDC9FRLmchgYQBEACgklMklJy3J1U542h3ofmkH5otjNaWv14oVr2yNdOxhlIG\nDYTb9vuLL1lAwxOB7JW6sgPbS9TmiCU6ZBYDeQDmfth7yPWK3/Epmd5wmXDENqra\njoZcpNSvvMnescS0MJWsSF2BHJiwPJewuOCAiL0EXGYVNB7z54kAt342okScNDK2\ndS6/ddjVKFsSi73HmLqQk7wmYpZuqIGoJQXH+E2to8h19e35YxOEsnG2DcVyC7xZ\nqHeUfuM9BTVJmUvqFdovz3lYJ+xg2CjBf8u0jRKOhhufS8JAu9H2ye9dWPktslMF\nRjfRbTAwryVGYmajnlmfoge+OD0XsubSaT79BixZ6xwXgA8xrCvM8in8ZeYsug66\nrgA/I7sO2PPQBh+FNVfuxVVr4MC1Nehk3/JghYzF9Ip7uAvoB9bzi0Yx7L3wGY8i\nr5Rss81IIYvZY4NmPwsOkeX+v9k6GbrcBDa521nl9gz3Ll9Q59jicZBaNyuIvJ3f\nP/bmh1nZc9CM+uIP3A5e/5tUTS5E7judEmOeqlotOjZGdoqyGsG1VqJcrcyTzscY\n8LxCIJtQEeM4KoptKaIXt0Mu/puMzQxIpcx9eFDZ+SE7Cl1QXC6HRLW5N99AuD5f\nSmxquKsmc+xB+gNGkYuySeTqfklK3FLTvISXZmoAQKgqdgO0d+hpCpOQ9lkprtJc\nAbMyytjCe+RLnIWHXi1hjQyspcF8JvBgnRp0zWEZwn+C7QI7ChHlSIrudMohS76L\nN2rF646oaFcxr8mDHy9bebQDXlWahbDB/2jFm3/SuyARtKSg8/PaNcuh+c8=\n=LxIo\n-----END PGP MESSAGE-----",
"fp": "4BE7925262289B476DBBC17B76FD3810215AE097"
}
],
"unencrypted_suffix": "_unencrypted",
"version": "3.11.0"
}
}

View file

@ -1,5 +1,5 @@
{ {
"data": "ENC[AES256_GCM,data:8MWVw/6bXo/1lp3IKzN/9rt3RKoU+2bv8voov+CLQzYWZ8yzOCp3ZxtK1qT8ol4oalTdf5KLnVcHjBCrI2vECO10otXQMmr7oyDpe6ORvIFSSjc3wCfA5Ddaot4qd7Eqwg261mjk2xtk+rNG1mkIfshMDXwX0GKXEocp7kGFncagMNB5armJjMC/HeukQMi7yxe5ahpz4K10/mkQiluZKVYxzzFkBMAyAUgzNYJxRbxnalq0nNmtb7pSHaVJk0JnapFEy2Jnswl8NmbmmC7O91EdDxEWUX6MRI9DMoLehFcU/Ij/Nn994jC3RNywgkPDv29uEvz5BPw3y9KNYrqzuCj0GFTODgNBykjw/fmmYPfSfgXrpS4QRE3ZklLsFvADPMIwnW7F65XCx7VVy5j9OGT3NObdwweFpsqh1+gyIq/Ity/RpkQ6uqqseRclO1vQYAqDzuh1SOi3SBP3C7J2HNMfJy5TkhzyzRTBItaYbKqVPWm1nsBf8ZldCQ==,iv:wYfg9ZesEPMsF9GbM2r4vZoiOABPRyWOHUrZJMetPVQ=,tag:pJJ7yGSme2EXmk4duQ+0RA==,type:str]", "data": "ENC[AES256_GCM,data:UKXEKxP1SDqQWktd3eQzkoqsk6k3m9Rj+JNk3xmdZmp5p+pXnY+uDltSIL2PTsOy7wtf4gp16jze1PhHvYojuN2nnou/D1KJALPHBgGiR8CgBlbX5nrCbtHrs4SZq+M7QihRV8lsG8gU0aIm0lDO83cJ0boUfnZdexDPjcuhYJj5nmgOG1bV60LOJNg7yn//hlVhovrf7ygXOk9HirDMmK9MVkKw5utD7iE4Cm7txrK1z9rQLJYM3kzwsWJAGkIc/IbI4Css10ScNK9VMKU4B596Dv2eCHvSIUJ8Y8AJrE/1+jp8XQW4aUMcFsbKpwjL2mOm0DSFupr/D60vJ1j5ovEIT4Vt51H4cpcBduBUCHoRZ1S/fZePxYaPunEI9lJVSQeANGevqXmvd8SSpO8YFN2S06CsFcx8hadQpq79uD7hm4tZzNUFOm2fytY9WMl0YWlSM4g3U30tKVVo+RMmm43oMaStOiyXUyohXjKY5QJqI+rJRRifKUhfze4Z4aGn,iv:nOU57gwkc3hld/+IqqHYtEiJYXzOFwTaG6cNEl7ZNHk=,tag:kRp580c9haQSQmOw2hBvrw==,type:str]",
"sops": { "sops": {
"age": [ "age": [
{ {
@ -7,8 +7,8 @@
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqdEhDamZTRUhQZFNDTTl4\nVVVNNGZXa2h2THVzY0JWMjE2WjNJT0ZoblV3ClYzeEt4c0dWRzlISnN3NGthR21M\nTEtDQ011dFdhRVdPWlpweS9ma0N3dmsKLS0tIHFPQzQ5VzkyODZyY1JpcE4xR2Nl\nY2MrSERXTWkvNVZCR2xHUGh4ZXMvYTgK7pxPjnh3idl4QzBkR6LHyRskgqA3apS2\nkbg7As6wlEs34TAO8reyZknKTUd3Xif1v9RXiTcu1sEKHqkcqEoDog==\n-----END AGE ENCRYPTED FILE-----\n" "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqdEhDamZTRUhQZFNDTTl4\nVVVNNGZXa2h2THVzY0JWMjE2WjNJT0ZoblV3ClYzeEt4c0dWRzlISnN3NGthR21M\nTEtDQ011dFdhRVdPWlpweS9ma0N3dmsKLS0tIHFPQzQ5VzkyODZyY1JpcE4xR2Nl\nY2MrSERXTWkvNVZCR2xHUGh4ZXMvYTgK7pxPjnh3idl4QzBkR6LHyRskgqA3apS2\nkbg7As6wlEs34TAO8reyZknKTUd3Xif1v9RXiTcu1sEKHqkcqEoDog==\n-----END AGE ENCRYPTED FILE-----\n"
} }
], ],
"lastmodified": "2025-12-03T16:34:02Z", "lastmodified": "2025-12-22T01:24:25Z",
"mac": "ENC[AES256_GCM,data:OBETnq727ZC90fB5eZsgGGpLz8tImqaRH4LEQsxzDWbLBeGz/eFTBAHiB5MRHV1X87M2RLgtLsylu58AKmctPxQtAwuDl/oy6AIyGhEbK0bohzryHX7hv4JlWasTWoBg64nCu63YlvuWLiLPNOuqDe6ODa7kLfk+SW8rOoVzJSc=,iv:+5SgpVThJnJUeqZUc2Sn1nkYjnaDGMjjRaSgn0gDCo8=,tag:lIsAjeaO9R6zluwdibD2BQ==,type:str]", "mac": "ENC[AES256_GCM,data:NtGHAadNGMfyCOqiaE/XRZqu4CnQ1IujgI3/IraY6E3luqzFVxJk/CgWD2rjbhLmaL7hd3Tay2LjL5uFxzM7kAE9QaaZtcxYKbudhznUdi/UEZ2ZtqyXqafXfCjEVbETaTAP3YGmQwJ/kAMj+FZp9yx7d6B8SVqWu1PatJGsOIA=,iv:OW6Xsr2MmEJq70TnEIJFgwLi3iMmKFV2Fy05a5G6Ibw=,tag:8KtNH6tEj/rQoht7FRDN3Q==,type:str]",
"pgp": [ "pgp": [
{ {
"created_at": "2025-12-01T23:06:36Z", "created_at": "2025-12-01T23:06:36Z",

View file

@ -26,7 +26,15 @@
isBtrfs = true; isBtrfs = true;
isNixos = true; isNixos = true;
isLinux = true; isLinux = true;
proxyHost = "eagleland"; proxyHost = "twothreetunnel"; # mail shall not be proxied through twothreetunnel
server = {
wireguard.interfaces = {
wgProxy = {
isClient = true;
serverName = "twothreetunnel";
};
};
};
}; };
} // lib.optionalAttrs (!minimal) { } // lib.optionalAttrs (!minimal) {
@ -34,6 +42,8 @@
mailserver = true; mailserver = true;
dns-hostrecord = true; dns-hostrecord = true;
postgresql = true; postgresql = true;
nginx = true;
wireguard = true;
}; };
swarselprofiles = { swarselprofiles = {

View file

@ -31,6 +31,16 @@
rootDisk = "/dev/sda"; rootDisk = "/dev/sda";
swapSize = "8G"; swapSize = "8G";
networkKernelModules = [ "igb" ]; networkKernelModules = [ "igb" ];
server = {
wireguard.interfaces = {
wgHome = {
isServer = true;
peers = [
"winters"
];
};
};
};
}; };
} // lib.optionalAttrs (!minimal) { } // lib.optionalAttrs (!minimal) {
@ -42,7 +52,7 @@
swarselmodules = { swarselmodules = {
server = { server = {
nginx = lib.mkForce false; # we get this from the server profile wireguard = true;
}; };
}; };

View file

@ -1,5 +1,5 @@
{ {
"data": "ENC[AES256_GCM,data:0KCJFnho4v+hEcPsJkK3bSUaSZnaOcXDKIQ5loWxmRkvEYxoDgOEgcgnm9zzuZWGwxPLeN2HxeRIWsG7rDk5xxTiRF4rIVUvIObeDChMDsgL2G26VVzYc4+Vm7kT6GHINDiKMGspktNQRhqCh/0HaGkle62z9lBPn3IO+c/1dumWI6UwC9zqa9PTcsH8nWy8lpovdhD7B4+A/aPZhnC2qpBZGmveh3dJe+zR/iiRRqjFgQ1rdQr5USjQjA2wJvKzx1HdkRbw9OXnINdMeVGi05SItsRz0KekzpCwq2wwhyOon8Qlour4CugV20w+csbsqmbXdGaaB2BK4oMWJh74lb77HBk0zbWCXw==,iv:P9lXQtmHkq5q6BkKuF8N/Yvm3gul4SQ7bYqS7nzNIyU=,tag:lixSTKRQ5WJOozWfTj6V+A==,type:str]", "data": "ENC[AES256_GCM,data:mi/EbLHjvOmJyK30E719clNAN/hq6FCS1ld0pLG4ahuuDkOfgbUr0vPEmhlxoIrkmD3HNkDufTlFsWezZ9s6OiZKS1ASRSPI96Js43BwVLotub1/YJaV0JMFNVoKTo9ag/5soaAbta0GNq0wExIjBNaVExCmNOs3puXCod4nOI9qmcn/ytx+98+3iZk8p87NTauX5W3jb87QjMqucWaEWxo4DLerkLI54baEMKJUULYRO4/BJ102pFq1twOVNm7v4R8FLbjknNL0A5T2ymmAJqOSpRLApcFJjSda6JkVoMV380a7Wa9cawLF/9xHRJn2K5R5uRx7JTLxL2VuW1olYMSrImO/d08277ZHymxeV07nJCyO/Y/0aR36P5YxJrUwNwKe31oR2RSm3Ns7u/DoC3gymYzbBLlBYMfocl34lL4EkK56W2qiGAfuEf6v/kXqS9X6si+rGDEaXkuS/0UEtsP7AxsUpRYHWngk/DGOUTzKXPkMsNv6IbxHSK4wpA1Xka93r13DrKTzcaoXOqEe+K9GWiJsJl0zAM4UEmYcXF7sh7WzYeeY3D1RT7nt4I0AHaPkt7PKsdo/DR3xQPUluQ553vVUe2rAMokqRVHHzM5OuGwng5iLb3u0fZaD,iv:bmp/x16E/gRXCDqcg5sUt+DRFCRsHIO0/01nr+uFR7Y=,tag:wFw8Efid68B88gTkAbCzuw==,type:str]",
"sops": { "sops": {
"age": [ "age": [
{ {
@ -7,8 +7,8 @@
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXc3VHa0p2MVdIdHRrbEVi\ndUwxMXA3cFpDODA0Z0MyUC9aemF4U2RXeUhrCmZjSDBLZ0twRk5rZG16blorQVVZ\nRE5SNE51bGlhYTVqcThFUVIvTWxwOW8KLS0tIEVHZ3Z6VVZHK2FUQWZQNVlOTkpL\nYUpNUSsyQllQL0lUa0FaODZiSjBDSk0KSJHdYoiOuma7YFjLpssAgw8BfBo5tl+o\nRvNt9rsXUlXEwMlcmYpkgUlsSAJnus+uE9AdBSvTyFRb9Wo696YFRg==\n-----END AGE ENCRYPTED FILE-----\n" "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXc3VHa0p2MVdIdHRrbEVi\ndUwxMXA3cFpDODA0Z0MyUC9aemF4U2RXeUhrCmZjSDBLZ0twRk5rZG16blorQVVZ\nRE5SNE51bGlhYTVqcThFUVIvTWxwOW8KLS0tIEVHZ3Z6VVZHK2FUQWZQNVlOTkpL\nYUpNUSsyQllQL0lUa0FaODZiSjBDSk0KSJHdYoiOuma7YFjLpssAgw8BfBo5tl+o\nRvNt9rsXUlXEwMlcmYpkgUlsSAJnus+uE9AdBSvTyFRb9Wo696YFRg==\n-----END AGE ENCRYPTED FILE-----\n"
} }
], ],
"lastmodified": "2025-12-15T22:09:17Z", "lastmodified": "2025-12-22T09:31:39Z",
"mac": "ENC[AES256_GCM,data:C2Jwz+4Rz76ZVa/kT1OxtNp0gvdrEDd7QmlIgDv3WeocVb05TaWKap1Z9ytjR1U8ZBpYWiLJ4DGNh3WEL3/kQpTuO3WsaTP/VWQiQdPn9AKpTPjlFblRxcAUiN8yxj+OVScvb8FUBfTCHSXII8oqHmGHRXbsduauGtiFh3RKK4Y=,iv:5YpsTABeP1TNh3CeAsDEG2WloCFXvTR5sESOTpvvRgY=,tag:ezYnBZ34kQZ/sAJLjUrD7g==,type:str]", "mac": "ENC[AES256_GCM,data:rOOL5gRTILzOnIU7LveEAI3HeLkf16wTZL4toxBqDiReWwXllCeUaFJ+n5awehit44LL1HrFVgZ/uUsnJBpF6WciPjXTKeRZsazhEKEuBhvcfJzvDQvj/ls5QsEXr/xuDmVaLNM7s7QCok+iefSS4Cu9IHhrmmdo1GyIw6gvNP0=,iv:pDnLtzMbGWR0PnIshenuNNvHIglvNFD+DJuUOapWGQI=,tag:9azzi8367Nq0Z0yGW8H5sg==,type:str]",
"pgp": [ "pgp": [
{ {
"created_at": "2025-12-15T22:09:23Z", "created_at": "2025-12-15T22:09:23Z",

View file

@ -0,0 +1,48 @@
wireguard-private-key: ENC[AES256_GCM,data:DBCK92h8mGxDshB5OIEbyUENc6a4jmvzKPvljUn50AM1I5vBm/bSTDRStIM=,iv:K/OiPnAlXNt3RqBiBiiZqIY8vqsIw0kmKE+aeeVhr+Q=,tag:eloCJ7yjI2tpHMxwNxZDDw==,type:str]
sops:
age:
- recipient: age1wmx8y2hs83j2u5srdnfxljrzxm8jtxl6fr0mq7xf2ldxyglpzf2qq89rpx
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAwTzZxNUdxbWUzbkp5eDE4
a3NGaWwrRXZxaXRvTmJjQUZHZU5wY3FpTTNrCmNxN21hU0dBd2piZUNCNndNaUNo
K252RGYyWVpXanZiVGMveXRnc0ViOFEKLS0tIFQ1T0dXUjlYdUNOcXJYZzA2YmtN
YWlkK0xrclpXYTkxUXFiNGMxU1NnMGcKCZzLfTPjeeGxyD43dOGDYsQVsw24cyHI
jz0B9VV07p33OP448eLyLgwpVFaNG0q+hXPH+0fb3V3foBT2QSeuPA==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2025-12-22T08:58:44Z"
mac: ENC[AES256_GCM,data:GnnNiw5DwXDCXEWqMa6eGYVNK4GyNvoNf9WK5wYE+uT8nolKD/pFEjqt++vHHlmEbPePhErAAu2vr7QGH/p8c+oEOEjiLJicMxJ72Bx8+5RLe4WuKO3GLTizgCy2f9Fr3gDWaKG8W9XF6xVzwPzzguRpfo1F0fmrPW6/EiGJDJ0=,iv:DWclKhUVp9UYc0F1J1k5+Y80dPK/RXoPDmylYlbmtiE=,tag:VgBHoVWPhOIwn7vuDwxKSw==,type:str]
pgp:
- created_at: "2025-12-22T08:56:58Z"
enc: |-
-----BEGIN PGP MESSAGE-----
hQIMAwDh3VI7VctTAQ/9E8KBoKOUyeIflZzmSriaoQ2/I0EnqKd9cLLFyqFFd4Gp
ZyOfaTqQE9/NWOG3KkG3iuHyCEdHjP14QolJDPPfuqjVnIkc0hKJ/TqwWb5OXurZ
hbkFZEYtuGWXGNugL0T/BnSUqXhd5sFBJueZD0LU7xBsmaDqMFlY//iheNEgq0RA
a3HeQL9gH4d1eUPje9XfcJ+onj9yYgejQ905ZIOAyrYTLVjnSc9HKJ3kz+rpin1J
2JHULBZEzigNiFXE2XmAatIM6PNBVJ21VL7CEPTt/qauRVHLsrz4PKcR/VMTzwJ/
A0hdMrYbYRKOL0rHDYyjpoeuKsUDNV0Gi//WQDXN9DGMREG5P4PH7+yPBcc+vgLK
E7B6RJcUFyuRh/n/KPGzKk1KX3KOQMjIKUaUGy7Ru91K8rG+/EH1ker6csDpe2aY
bYjtPnjiIvd/dR++JLALQJfCuFC6pUhGAC71Bchr4U2Rg+s9pRZBOYco7pJMJubd
rkt61MYFNpcZkyQ9mYAVCd13JcmoTsAtwmUkdU098tfCVA8sMRgFF1f2DK8iyRrq
jfh6pX1/UqFtOug8hElBJHMQkl9eAKla6COQeGtZC3LkxkKhkNLTcMLf4I5Tzf8o
ftxFw1eW4174Psg9vo+/T1zcOYQTVIUfnlPuK/oiCJIAWZ2U92HnCa9pwQe8nkSF
AgwDC9FRLmchgYQBD/4lFaFk9tlyBnTWY5yWJmpcV1gPSwLyeMnax/89/Nnixu1/
205CvMGEReFEQ4CDTp+WXwp7DA3PKqhg/hEq/x9cmH0kAkQg1n9QoJcd2UzDadfp
89ABsW5fBZJSLdHn3P06VIihe516GnsDA/KL88PdkYXpElgfqWXC8g2URKW6QeO5
j/XzOXDiMdO2+K37NcbwSQsMd0pc2BAJ4mmjvjm0aZe6ddF1917WYFkOZi09clNh
iYW8Vk4hmOkGqEO3zNjQkzZ6Ra9Cm4qr1BG7k+n4sxuwoae2T14/DlCSYh/llSTw
N25tWEeXeaAtQgVwoWYLrmSdCKYtxyACPrt6uEYaGE7wbXgBgCX91HuznlHiUvnG
uagiFMxr0x4G2Q+C8OuptKBneBcR6a21q3HaGdl/99F3fM7C2bvzv2y+ZScBP6fH
LvZjF/r3qrLONCqtaQ4Kw9LPzow8wMkCkshC7K0KNRq10ww7s9kbY8io4+QVLv3p
ZHbN+U+9BheVOAF8uX8V+OQfeFdp0VTbPZa7v1mLdbjshPNi7SEhlCjrtB8yqRtd
cl2tinqfWAosYt0xdUmH9uoY7bz9+BKIZ6FVl1huP2DEa5JAjnVItyLG+n2GpIqN
1SBaC/OCbJFawPmZgaWou+kxpLr7hu6kmPdCcdtHa4TYuanLkOTk0r0mztzhjNJe
Af5UVQLJJ7tduvLAB+vh/z91qgv0ftVDq4Kkr7Ma37OYAx4VzuHwEXNLKu2C6CwE
M7sp4ZglesyABMbOEhwxqg/kCYGS76kThwkrJfrgf82FgnMdUyYCMhhgy6iFow==
=izPI
-----END PGP MESSAGE-----
fp: 4BE7925262289B476DBBC17B76FD3810215AE097
unencrypted_suffix: _unencrypted
version: 3.11.0

View file

@ -43,10 +43,6 @@
server = true; server = true;
}; };
swarselmodules.server = {
nginx = lib.mkForce false;
};
microvm.vms = microvm.vms =
let let
mkMicrovm = guestName: { mkMicrovm = guestName: {

View file

@ -1,4 +1,4 @@
{ self, lib, minimal, ... }: { self, lib, minimal, globals, ... }:
{ {
imports = [ imports = [
@ -15,11 +15,10 @@
loader.efi.canTouchEfiVariables = true; loader.efi.canTouchEfiVariables = true;
}; };
# globals.hosts.${config.node.name}.ipv4 = config.repo.secrets.local.ipv4; networking.hosts = {
# globals.networks.home.hosts.${config.node.name} = { ${globals.networks.home-lan.hosts.hintbooth.ipv4} = [ "server.hintbooth.${globals.domains.main}" ];
# ipv4 = config.repo.secrets.local.home-ipv4; ${globals.networks.home-lan.hosts.hintbooth.ipv6} = [ "server.hintbooth.${globals.domains.main}" ];
# mac = config.repo.secrets.local.home-mac; };
# };
swarselsystems = { swarselsystems = {
info = "ASRock J4105-ITX, 32GB RAM"; info = "ASRock J4105-ITX, 32GB RAM";
@ -32,9 +31,15 @@
isNixos = true; isNixos = true;
proxyHost = "twothreetunnel"; proxyHost = "twothreetunnel";
server = { server = {
wireguard = { wireguard.interfaces = {
isClient = true; wgProxy = {
serverName = "twothreetunnel"; isClient = true;
serverName = "twothreetunnel";
};
wgHome = {
isClient = true;
serverName = "hintbooth";
};
}; };
restic = { restic = {
bucketName = "SwarselWinters"; bucketName = "SwarselWinters";
@ -67,37 +72,36 @@
swarselmodules.server = { swarselmodules.server = {
diskEncryption = lib.mkForce false; diskEncryption = lib.mkForce false;
wireguard = lib.mkDefault true; nginx = true; # for php stuff
nfs = lib.mkDefault true; acme = false; # cert handled by proxy
nginx = lib.mkDefault true; wireguard = true;
kavita = lib.mkDefault true;
restic = lib.mkDefault true; nfs = true;
jellyfin = lib.mkDefault true; kavita = true;
navidrome = lib.mkDefault true; restic = true;
spotifyd = lib.mkDefault true; jellyfin = true;
mpd = lib.mkDefault true; navidrome = true;
postgresql = lib.mkDefault true; spotifyd = true;
matrix = lib.mkDefault true; mpd = true;
nextcloud = lib.mkDefault true; postgresql = true;
immich = lib.mkDefault true; matrix = true;
paperless = lib.mkDefault true; nextcloud = true;
transmission = lib.mkDefault true; immich = true;
syncthing = lib.mkDefault true; paperless = true;
grafana = lib.mkDefault true; transmission = true;
emacs = lib.mkDefault true; syncthing = true;
freshrss = lib.mkDefault true; grafana = true;
jenkins = lib.mkDefault false; emacs = true;
kanidm = lib.mkDefault true; freshrss = true;
firefly-iii = lib.mkDefault true; kanidm = true;
koillection = lib.mkDefault true; firefly-iii = true;
radicale = lib.mkDefault true; koillection = true;
atuin = lib.mkDefault true; radicale = true;
forgejo = lib.mkDefault true; atuin = true;
ankisync = lib.mkDefault true; forgejo = true;
# snipeit = lib.mkDefault false; ankisync = true;
homebox = lib.mkDefault true; homebox = true;
opkssh = lib.mkDefault true; opkssh = true;
garage = lib.mkDefault false;
}; };
} }

2358
index.html

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,24 @@
{ lib, config, pkgs, ... }:
{
options.swarselmodules.attic-store-push = lib.mkEnableOption "enable automatic attic store push";
config = lib.mkIf config.swarselmodules.attic-store-push {
systemd.user.services.attic-store-push = {
Unit = {
Description = "Attic store pusher";
Requires = [ "graphical-session.target" ];
After = [ "graphical-session.target" ];
PartOf = [ "graphical-session.target" ];
};
Install = {
WantedBy = [ "graphical-session.target" ];
};
Service = {
ExecStart = "${lib.getExe pkgs.attic-client} watch-store ${config.swarselsystems.mainUser}:${config.swarselsystems.mainUser}";
};
};
};
}

View file

@ -33,6 +33,8 @@
endme endme
git-replace git-replace
prstatus prstatus
swarsel-gens
swarsel-switch
]; ];
}; };
} }

View file

@ -18,9 +18,9 @@
serverAliveCountMax = 3; serverAliveCountMax = 3;
hashKnownHosts = false; hashKnownHosts = false;
userKnownHostsFile = "~/.ssh/known_hosts"; userKnownHostsFile = "~/.ssh/known_hosts";
controlMaster = "auto"; controlMaster = "no";
controlPath = "~/.ssh/master-%r@%n:%p"; controlPath = "~/.ssh/master-%r@%n:%p";
controlPersist = "5m"; controlPersist = "no";
}; };
} // confLib.getConfig.repo.secrets.common.ssh.hosts; } // confLib.getConfig.repo.secrets.common.ssh.hosts;
}; };

View file

@ -202,7 +202,13 @@ in
description = "List of external dns nameservers"; description = "List of external dns nameservers";
}; };
}; };
}; };
}; };
}; };

View file

@ -1,5 +1,5 @@
# largely based on https://github.com/oddlama/nix-config/blob/main/modules/secrets.nix # largely based on https://github.com/oddlama/nix-config/blob/main/modules/secrets.nix
{ config, inputs, lib, ... }: { config, inputs, lib, nodes, ... }:
let let
# If the given expression is a bare set, it will be wrapped in a function, # If the given expression is a bare set, it will be wrapped in a function,
# so that the imported file can always be applied to the inputs, similar to # so that the imported file can always be applied to the inputs, similar to
@ -53,7 +53,7 @@ in
secrets = lib.mkOption { secrets = lib.mkOption {
readOnly = true; readOnly = true;
default = lib.mapAttrs (_: x: importEncrypted x inputs) config.repo.secretFiles; default = lib.mapAttrs (_: x: importEncrypted x { inherit lib nodes inputs; }) config.repo.secretFiles;
type = lib.types.unspecified; type = lib.types.unspecified;
description = "Exposes the loaded repo secrets. This option is read-only."; description = "Exposes the loaded repo secrets. This option is read-only.";
}; };

View file

@ -1,4 +1,4 @@
{ config, globals, ... }: { lib, config, globals, ... }:
{ {
topology.self = { topology.self = {
icon = lib.mkIf config.swarselsystems.isCloud "devices.cloud-server"; icon = lib.mkIf config.swarselsystems.isCloud "devices.cloud-server";

View file

@ -0,0 +1,45 @@
{ self, pkgs, lib, config, globals, ... }:
let
inherit (config.repo.secrets.common) dnsProvider dnsBase dnsMail;
sopsFile = self + "/secrets/nginx/acme.json";
in
{
options.swarselmodules.server.acme = lib.mkEnableOption "enable acme on server";
config = lib.mkIf config.swarselmodules.server.acme {
environment.systemPackages = with pkgs; [
lego
];
sops = {
secrets = {
acme-creds = { format = "json"; key = ""; group = "acme"; inherit sopsFile; mode = "0660"; };
};
templates."certs.secret".content = ''
ACME_DNS_API_BASE = ${dnsBase}
ACME_DNS_STORAGE_PATH=${config.sops.secrets.acme-creds.path}
'';
};
users.groups.acme.members = lib.mkIf config.swarselmodules.server.nginx [ "nginx" ];
security.acme = {
acceptTerms = true;
defaults = {
inherit dnsProvider;
email = dnsMail;
environmentFile = "${config.sops.templates."certs.secret".path}";
reloadServices = [ "nginx" ];
dnsPropagationCheck = true;
};
certs."${globals.domains.main}" = {
domain = "*.${globals.domains.main}";
};
};
environment.persistence."/persist" = lib.mkIf config.swarselsystems.isImpermanence {
directories = [{ directory = "/var/lib/acme"; }];
};
};
}

View file

@ -0,0 +1,54 @@
{ lib, config, pkgs, globals, ... }:
{
options.swarselmodules.server.attic-setup = lib.mkEnableOption "enable attic setup";
config = lib.mkIf config.swarselmodules.server.attic-setup {
environment.systemPackages = with pkgs; [
attic-client
];
sops = {
secrets = {
attic-cache-key = { };
};
templates = {
"attic-env".content = ''
DOMAIN=https://${globals.services.attic.domain}
TOKEN=${config.sops.placeholder.attic-cache-key}
'';
};
};
systemd.services.attic-cache-setup = {
description = "Ensure attic is authenticated to cache";
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
serviceConfig = {
Type = "oneshot";
EnvironmentFile = [
config.sops.templates.attic-env.path
];
};
script =
let
attic = lib.getExe pkgs.attic-client;
in
''
set -eu
if ${attic} cache info ${config.swarselsystems.mainUser} >/dev/null 2>&1; then
echo "cache already authenticated"
exit 0
fi
echo "cache not authenticated, attempting login..."
${attic} login ${config.swarselsystems.mainUser} "$DOMAIN" "$TOKEN" --set-default
${attic} use ${config.swarselsystems.mainUser}
'';
};
};
}

View file

@ -10,6 +10,11 @@ let
"/persist/${hostKeyPathBase}" "/persist/${hostKeyPathBase}"
else else
"${hostKeyPathBase}"; "${hostKeyPathBase}";
# this key is only used only for ssh to stage 1 in initial provisioning (in nix store)
generatedHostKey = pkgs.runCommand "initrd-hostkey" { } ''
${pkgs.openssh}/bin/ssh-keygen -t ed25519 -N "" -f $out
'';
in in
{ {
options.swarselmodules.server.diskEncryption = lib.mkEnableOption "enable disk encryption config"; options.swarselmodules.server.diskEncryption = lib.mkEnableOption "enable disk encryption config";
@ -20,15 +25,15 @@ in
config = lib.mkIf (config.swarselmodules.server.diskEncryption && config.swarselsystems.isCrypted) { config = lib.mkIf (config.swarselmodules.server.diskEncryption && config.swarselsystems.isCrypted) {
system.activationScripts."createPersistentStorageDirs" = lib.mkIf config.swarselsystems.isImpermanence { # as soon as we hit a stable system, we will use a persisted key
deps = [ "ensureInitrdHostkey" ]; # @future me: dont mkIf this to minimal, we need to create this as soon as possible
}; system.activationScripts.ensureInitrdHostkey = {
system.activationScripts.ensureInitrdHostkey = lib.mkIf (config.swarselprofiles.server || minimal) {
text = '' text = ''
[[ -e ${hostKeyPath} ]] || ${pkgs.openssh}/bin/ssh-keygen -t ed25519 -N "" -f ${hostKeyPath} [[ -e ${hostKeyPath} ]] || ${pkgs.openssh}/bin/ssh-keygen -t ed25519 -N "" -f ${hostKeyPath}
''; '';
deps = [ deps = [
"etc" "users"
"createPersistentStorageDirs"
]; ];
}; };
@ -41,7 +46,7 @@ in
"ip=${localIp}::${gatewayIp}:${subnetMask}:${config.networking.hostName}::none" "ip=${localIp}::${gatewayIp}:${subnetMask}:${config.networking.hostName}::none"
]; ];
initrd = { initrd = {
secrets."${hostKeyPathBase}" = lib.mkIf (!minimal) hostKeyPathBase; secrets."/tmp${hostKeyPathBase}" = if minimal then (lib.mkForce generatedHostKey) else (lib.mkForce hostKeyPath); # need to mkForce this or it behaves stateful
availableKernelModules = config.swarselsystems.networkKernelModules; availableKernelModules = config.swarselsystems.networkKernelModules;
network = { network = {
enable = true; enable = true;
@ -53,34 +58,13 @@ in
''command="/bin/systemctl default" ${builtins.readFile "${self}/secrets/public/ssh/yubikey.pub"}'' ''command="/bin/systemctl default" ${builtins.readFile "${self}/secrets/public/ssh/yubikey.pub"}''
''command="/bin/systemctl default" ${builtins.readFile "${self}/secrets/public/ssh/magicant.pub"}'' ''command="/bin/systemctl default" ${builtins.readFile "${self}/secrets/public/ssh/magicant.pub"}''
]; ];
hostKeys = [ hostKeyPathBase ]; hostKeys = [ "/tmp${hostKeyPathBase}" ]; # use a tmp file otherwise persist mount will be unhappy
}; };
# postCommands = ''
# echo 'cryptsetup-askpass || echo "Unlock was successful; exiting SSH session" && exit 1' >> /root/.profile
# '';
}; };
systemd = { systemd = {
initrdBin = with pkgs; [ initrdBin = with pkgs; [
cryptsetup cryptsetup
]; ];
# NOTE: the below does put the text into /root/.profile, but the command will not be run
# services = {
# unlock-luks = {
# wantedBy = [ "initrd.target" ];
# after = [ "network.target" ];
# before = [ "systemd-cryptsetup@cryptroot.service" ];
# path = [ "/bin" ];
# serviceConfig = {
# Type = "oneshot";
# RemainAfterExit = true;
# };
# script = ''
# echo "systemctl default" >> /root/.profile
# '';
# };
# };
}; };
}; };
}; };

View file

@ -1,9 +1,13 @@
{ lib, config, globals, dns, confLib, ... }: { lib, config, globals, dns, confLib, ... }:
let let
inherit (config.swarselsystems) sopsFile; inherit (config.swarselsystems) sopsFile;
inherit (confLib.gen { name = "mailserver"; dir = "/var/lib/dovecot"; user = "virtualMail"; group = "virtualMail"; port = 443; }) serviceName serviceDir servicePort serviceUser serviceGroup serviceDomain serviceProxy proxyAddress4 proxyAddress6; inherit (confLib.gen { name = "mailserver"; dir = "/var/lib/dovecot"; user = "virtualMail"; group = "virtualMail"; port = 443; }) serviceName serviceDir servicePort serviceUser serviceGroup serviceAddress serviceDomain serviceProxy proxyAddress4 proxyAddress6;
inherit (config.repo.secrets.local.mailserver) user1 alias1_1 alias1_2 alias1_3 alias1_4 user2 alias2_1 alias2_2 user3; inherit (config.repo.secrets.local.mailserver) user1 alias1_1 alias1_2 alias1_3 alias1_4 user2 alias2_1 alias2_2 user3;
baseDomain = globals.domains.main; baseDomain = globals.domains.main;
roundcubeDomain = config.repo.secrets.common.services.domains.roundcube;
endpointAddress4 = globals.hosts.${config.node.name}.wanAddress4 or null;
endpointAddress6 = globals.hosts.${config.node.name}.wanAddress6 or null;
in in
{ {
options = { options = {
@ -12,12 +16,20 @@ in
config = lib.mkIf config.swarselmodules.server.${serviceName} { config = lib.mkIf config.swarselmodules.server.${serviceName} {
nodes.stoicclub.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = { nodes.stoicclub.swarselsystems.server.dns.${globals.services.${serviceName}.baseDomain}.subdomainRecords = {
"${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6; "${globals.services.${serviceName}.subDomain}" = dns.lib.combinators.host endpointAddress4 endpointAddress6;
"${globals.services.roundcube.subDomain}" = dns.lib.combinators.host proxyAddress4 proxyAddress6;
}; };
globals.services.${serviceName} = { globals.services = {
domain = serviceDomain; ${serviceName} = {
inherit proxyAddress4 proxyAddress6; domain = serviceDomain;
proxyAddress4 = endpointAddress4;
proxyAddress6 = endpointAddress6;
};
roundcube = {
domain = roundcubeDomain;
inherit proxyAddress4 proxyAddress6;
};
}; };
sops.secrets = { sops.secrets = {
@ -83,7 +95,7 @@ in
enable = true; enable = true;
# this is the url of the vhost, not necessarily the same as the fqdn of # this is the url of the vhost, not necessarily the same as the fqdn of
# the mailserver # the mailserver
hostName = serviceDomain; hostName = roundcubeDomain;
extraConfig = '' extraConfig = ''
$config['imap_host'] = "ssl://${config.mailserver.fqdn}"; $config['imap_host'] = "ssl://${config.mailserver.fqdn}";
$config['smtp_host'] = "ssl://${config.mailserver.fqdn}"; $config['smtp_host'] = "ssl://${config.mailserver.fqdn}";
@ -96,10 +108,11 @@ in
# the rest of the ports are managed by snm # the rest of the ports are managed by snm
networking.firewall.allowedTCPPorts = [ 80 servicePort ]; networking.firewall.allowedTCPPorts = [ 80 servicePort ];
nodes.${serviceProxy}.services.nginx = { services.nginx = {
virtualHosts = { virtualHosts = {
"${serviceDomain}" = { "${roundcubeDomain}" = {
enableACME = true; useACMEHost = globals.domains.main;
enableACME = false;
forceSSL = true; forceSSL = true;
acmeRoot = null; acmeRoot = null;
locations = { locations = {
@ -112,5 +125,31 @@ in
}; };
}; };
nodes.${serviceProxy}.services.nginx = {
upstreams = {
${serviceName} = {
servers = {
"${serviceAddress}:${builtins.toString servicePort}" = { };
};
};
};
virtualHosts = {
"${roundcubeDomain}" = {
useACMEHost = globals.domains.main;
forceSSL = true;
acmeRoot = null;
locations = {
"/" = {
proxyPass = "https://${serviceName}";
extraConfig = ''
client_max_body_size 0;
'';
};
};
};
};
};
}; };
} }

View file

@ -1,7 +1,5 @@
{ pkgs, lib, config, globals, ... }: { pkgs, lib, config, ... }:
let let
inherit (config.repo.secrets.common) dnsProvider dnsBase dnsMail;
serviceUser = "nginx"; serviceUser = "nginx";
serviceGroup = serviceUser; serviceGroup = serviceUser;
@ -81,40 +79,12 @@ in
}; };
}; };
config = lib.mkIf config.swarselmodules.server.nginx { config = lib.mkIf config.swarselmodules.server.nginx {
environment.systemPackages = with pkgs; [
lego
];
sops = lib.mkIf (config.node.name == config.swarselsystems.proxyHost) { swarselmodules.server.acme = lib.mkDefault true;
secrets = {
acme-creds = { format = "json"; key = ""; group = "acme"; sopsFile = config.node.secretsDir + "/acme.json"; mode = "0660"; };
};
templates."certs.secret".content = ''
ACME_DNS_API_BASE = ${dnsBase}
ACME_DNS_STORAGE_PATH=${config.sops.secrets.acme-creds.path}
'';
};
users.groups.acme.members = [ "nginx" ];
security.acme = lib.mkIf (config.node.name == config.swarselsystems.proxyHost) {
acceptTerms = true;
defaults = {
inherit dnsProvider;
email = dnsMail;
environmentFile = "${config.sops.templates."certs.secret".path}";
reloadServices = [ "nginx" ];
dnsPropagationCheck = true;
};
certs."${globals.domains.main}" = {
domain = "*.${globals.domains.main}";
};
};
networking.firewall.allowedTCPPorts = [ 80 443 ]; networking.firewall.allowedTCPPorts = [ 80 443 ];
environment.persistence."/persist" = lib.mkIf config.swarselsystems.isImpermanence { environment.persistence."/persist" = lib.mkIf config.swarselsystems.isImpermanence {
directories = [{ directory = "/var/lib/acme"; }];
files = [ dhParamsPathBase ]; files = [ dhParamsPathBase ];
}; };

View file

@ -3,7 +3,7 @@ with dns.lib.combinators; {
SOA = { SOA = {
nameServer = "soa"; nameServer = "soa";
adminEmail = "admin@${globals.domains.main}"; # this option is not parsed as domain (we cannot just write "admin") adminEmail = "admin@${globals.domains.main}"; # this option is not parsed as domain (we cannot just write "admin")
serial = 2025120506; # update this on changes for secondary dns serial = 2025122204; # update this on changes for secondary dns
}; };
useOrigin = false; useOrigin = false;
@ -13,7 +13,23 @@ with dns.lib.combinators; {
"srv" "srv"
] ++ globals.domains.externalDns; ] ++ globals.domains.externalDns;
CAA = letsEncrypt config.repo.secrets.common.dnsMail; CAA = [
{
issuerCritical = false;
tag = "issue";
value = "letsencrypt.org";
}
{
issuerCritical = false;
tag = "issuewild";
value = "letsencrypt.org";
}
{
issuerCritical = false;
tag = "iodef";
value = "mailto:${config.repo.secrets.common.dnsMail}";
}
];
A = [ config.repo.secrets.local.dns.homepage-ip ]; A = [ config.repo.secrets.local.dns.homepage-ip ];

View file

@ -11,9 +11,11 @@
emacs emacs
vim vim
sops sops
swarsel-deploy
tmux tmux
busybox busybox
swarsel-deploy
swarsel-gens
swarsel-switch
]; ];
}; };
} }

View file

@ -1,176 +1,217 @@
{ self, lib, pkgs, config, confLib, nodes, globals, ... }: { self, lib, pkgs, config, confLib, nodes, globals, ... }:
let let
wgInterface = "wg0"; inherit (confLib.gen {
inherit (confLib.gen { name = "wireguard"; port = 52829; user = "systemd-network"; group = "systemd-network"; }) servicePort serviceName serviceUser serviceGroup; name = "wireguard";
port = 52829;
user = "systemd-network";
group = "systemd-network";
}) servicePort serviceName serviceUser serviceGroup;
inherit (config.swarselsystems) sopsFile; inherit (config.swarselsystems) sopsFile;
wgSopsFile = self + "/secrets/repo/wg.yaml"; wgSopsFile = self + "/secrets/repo/wg.yaml";
inherit (config.swarselsystems.server.wireguard) peers isClient isServer serverName serverNetConfigPrefix ifName;
cfg = config.swarselsystems.server.wireguard;
inherit (cfg) interfaces;
ifaceList = builtins.attrValues interfaces;
in in
{ {
options = { options = {
swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} settings"; swarselmodules.server.${serviceName} =
lib.mkEnableOption "enable ${serviceName} settings";
swarselsystems.server.wireguard = { swarselsystems.server.wireguard = {
isServer = lib.mkEnableOption "set this as a wireguard server"; interfaces = lib.mkOption {
isClient = lib.mkEnableOption "set this as a wireguard client"; type = lib.types.attrsOf (lib.types.submodule ({ name, config, ... }: {
serverName = lib.mkOption { options = {
type = lib.types.str; isServer = lib.mkEnableOption "set this interface as a wireguard server";
default = ""; isClient = lib.mkEnableOption "set this interface as a wireguard client";
};
serverNetConfigPrefix = lib.mkOption { serverName = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = "${if nodes.${serverName}.config.swarselsystems.isCloud then nodes.${serverName}.config.node.name else "home"}"; default = "";
readOnly = true; description = "Hostname of the WireGuard server this interface connects to (when isClient = true).";
}; };
ifName = lib.mkOption {
type = lib.types.str; serverNetConfigPrefix = lib.mkOption {
default = wgInterface; type = lib.types.str;
}; default =
peers = lib.mkOption { let
type = lib.types.listOf lib.types.str; serverCfg = nodes.${config.serverName}.config;
default = [ ]; in
description = "Wireguard peer config names"; if serverCfg.swarselsystems.isCloud
then serverCfg.node.name
else "home";
readOnly = true;
description = "Prefix used to look up the server network in globals.networks.\"<prefix>-wg\".";
};
ifName = lib.mkOption {
type = lib.types.str;
default = name;
description = "Name of the WireGuard interface.";
};
peers = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "WireGuard peer config names (clients when this host is server, or additional peers).";
};
};
}));
default = { };
description = "WireGuard interfaces defined on this host.";
}; };
}; };
}; };
config = lib.mkIf config.swarselmodules.server.${serviceName} { config = lib.mkIf config.swarselmodules.server.${serviceName} {
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
wireguard-tools wireguard-tools
]; ];
sops = { sops.secrets =
secrets = { lib.mkMerge (
wireguard-private-key = { inherit sopsFile; owner = serviceUser; group = serviceGroup; mode = "0600"; }; [
# create this secret only if this is a simple client with only one peer (the server) {
"wireguard-${serverName}-${config.node.name}-presharedKey" = lib.mkIf (isClient && peers == [ ]) { sopsFile = wgSopsFile; owner = serviceUser; group = serviceGroup; mode = "0600"; }; # shared host private key
} wireguard-private-key = {
# create these secrets only if this host has multiple peers inherit sopsFile;
// lib.optionalAttrs (peers != [ ]) (builtins.listToAttrs (map owner = serviceUser;
(clientName: { group = serviceGroup;
name = "wireguard-${config.node.name}-${clientName}-presharedKey"; mode = "0600";
value = { sopsFile = wgSopsFile; owner = serviceUser; group = serviceGroup; mode = "0600"; }; };
}) }
peers)); ] ++ (map
}; (i:
let
simpleClientSecrets =
lib.optionalAttrs (i.isClient && i.peers == [ ]) {
"wireguard-${i.serverName}-${config.node.name}-${i.ifName}-presharedKey" = {
sopsFile = wgSopsFile;
owner = serviceUser;
group = serviceGroup;
mode = "0600";
};
};
multiPeerSecrets =
lib.optionalAttrs (i.peers != [ ]) (builtins.listToAttrs (map
(clientName: {
name = "wireguard-${config.node.name}-${clientName}-${i.ifName}-presharedKey";
value = {
sopsFile = wgSopsFile;
owner = serviceUser;
group = serviceGroup;
mode = "0600";
};
})
i.peers));
in
simpleClientSecrets // multiPeerSecrets
)
ifaceList)
);
networking = { networking = {
firewall.checkReversePath = lib.mkIf isClient "loose"; firewall.checkReversePath =
firewall.allowedUDPPorts = [ servicePort ]; lib.mkIf (lib.any (i: i.isClient) ifaceList) "loose";
# nat = lib.mkIf (config.swarselsystems.isCloud && isServer) {
# enable = true; firewall.allowedUDPPorts =
# enableIPv6 = true; lib.mkIf (lib.any (i: i.isServer) ifaceList) [ servicePort ];
# externalInterface = "enp0s6";
# internalInterfaces = [ ifName ];
# };
# interfaces.${ifName}.mtu = 1280; # the default (1420) is not enough!
}; };
systemd.network = { systemd.network = {
enable = true; enable = true;
networks."50-${ifName}" = { networks = lib.mkMerge (map
matchConfig.Name = ifName; (i:
linkConfig = { let
MTUBytes = 1408; # TODO: figure out where we lose those 12 bits (8 from pppoe maybe + ???) inherit (i) ifName;
}; in
# networkConfig = lib.mkIf (config.swarselsystems.isCloud && isServer) {
# IPv4Forwarding = true;
# IPv6Forwarding = true;
# };
address =
if isServer then [
globals.networks."${config.swarselsystems.server.netConfigPrefix}-wg".hosts.${config.node.name}.cidrv4
globals.networks."${config.swarselsystems.server.netConfigPrefix}-wg".hosts.${config.node.name}.cidrv6
] else [
globals.networks."${serverNetConfigPrefix}-wg".hosts.${config.node.name}.cidrv4
globals.networks."${serverNetConfigPrefix}-wg".hosts.${config.node.name}.cidrv6
];
};
netdevs."50-${ifName}" = {
netdevConfig = {
Kind = "wireguard";
Name = ifName;
};
wireguardConfig = {
ListenPort = lib.mkIf isServer servicePort;
# ensure file is readable by `systemd-network` user
PrivateKeyFile = config.sops.secrets.wireguard-private-key.path;
# To automatically create routes for everything in AllowedIPs,
# add RouteTable=main
RouteTable = lib.mkIf isClient "main";
# FirewallMark marks all packets send and received by wg0
# with the number 42, which can be used to define policy rules on these packets.
# FirewallMark = 42;
};
wireguardPeers = lib.optionals isClient [
{ {
PublicKey = builtins.readFile "${self}/secrets/public/wg/${serverName}.pub"; "50-${ifName}" = {
PresharedKeyFile = config.sops.secrets."wireguard-${serverName}-${config.node.name}-presharedKey".path; matchConfig.Name = ifName;
Endpoint = "server.${serverName}.${globals.domains.main}:${toString servicePort}"; linkConfig = {
# Access to the whole network is routed through our entry node. MTUBytes = 1408; # TODO: figure out where we lose those 12 bits (8 from pppoe maybe + ???)
PersistentKeepalive = 25; };
AllowedIPs =
let address =
wgNetwork = globals.networks."${serverNetConfigPrefix}-wg"; if i.isServer then [
in globals.networks."${config.swarselsystems.server.netConfigPrefix}-${ifName}".hosts.${config.node.name}.cidrv4
(lib.optional (wgNetwork.cidrv4 != null) wgNetwork.cidrv4) globals.networks."${config.swarselsystems.server.netConfigPrefix}-${ifName}".hosts.${config.node.name}.cidrv6
++ (lib.optional (wgNetwork.cidrv6 != null) wgNetwork.cidrv6); ] else [
} globals.networks."${i.serverNetConfigPrefix}-${ifName}".hosts.${config.node.name}.cidrv4
] ++ lib.optionals isServer (map globals.networks."${i.serverNetConfigPrefix}-${ifName}".hosts.${config.node.name}.cidrv6
(clientName: { ];
PublicKey = builtins.readFile "${self}/secrets/public/wg/${clientName}.pub"; };
PresharedKeyFile = config.sops.secrets."wireguard-${config.node.name}-${clientName}-presharedKey".path;
# PersistentKeepalive = 25;
AllowedIPs =
let
clientInWgNetwork = globals.networks."${config.swarselsystems.server.netConfigPrefix}-wg".hosts.${clientName};
in
(lib.optional (clientInWgNetwork.ipv4 != null) (lib.net.cidr.make 32 clientInWgNetwork.ipv4))
++ (lib.optional (clientInWgNetwork.ipv6 != null) (lib.net.cidr.make 128 clientInWgNetwork.ipv6));
}) })
peers); ifaceList);
}; netdevs = lib.mkMerge (map
(i:
let
inherit (i) ifName;
in
{
"50-${ifName}" = {
netdevConfig = {
Kind = "wireguard";
Name = ifName;
};
wireguardConfig = {
ListenPort = lib.mkIf i.isServer servicePort;
PrivateKeyFile = config.sops.secrets.wireguard-private-key.path;
RouteTable = lib.mkIf i.isClient "main";
};
wireguardPeers =
lib.optionals i.isClient [
{
PublicKey =
builtins.readFile "${self}/secrets/public/wg/${i.serverName}.pub";
PresharedKeyFile =
config.sops.secrets."wireguard-${i.serverName}-${config.node.name}-${i.ifName}-presharedKey".path;
Endpoint =
"server.${i.serverName}.${globals.domains.main}:${toString servicePort}";
PersistentKeepalive = 25;
AllowedIPs =
let
wgNetwork = globals.networks."${i.serverNetConfigPrefix}-${i.ifName}";
in
(lib.optional (wgNetwork.cidrv4 != null) wgNetwork.cidrv4)
++ (lib.optional (wgNetwork.cidrv6 != null) wgNetwork.cidrv6);
}
]
++ lib.optionals i.isServer (map
(clientName: {
PublicKey =
builtins.readFile "${self}/secrets/public/wg/${clientName}.pub";
PresharedKeyFile =
config.sops.secrets."wireguard-${config.node.name}-${clientName}-${i.ifName}-presharedKey".path;
AllowedIPs =
let
clientInWgNetwork =
globals.networks."${config.swarselsystems.server.netConfigPrefix}-${i.ifName}".hosts.${clientName};
in
(lib.optional (clientInWgNetwork.ipv4 != null)
(lib.net.cidr.make 32 clientInWgNetwork.ipv4))
++ (lib.optional (clientInWgNetwork.ipv6 != null)
(lib.net.cidr.make 128 clientInWgNetwork.ipv6));
})
i.peers);
};
})
ifaceList);
}; };
# networking = {
# wireguard = {
# enable = true;
# interfaces = {
# wg1 = {
# privateKeyFile = config.sops.secrets.wireguard-private-key.path;
# ips = [ "192.168.178.201/24" ];
# peers = [
# {
# publicKey = "PmeFInoEJcKx+7Kva4dNnjOEnJ8lbudSf1cbdo/tzgw=";
# presharedKeyFile = config.sops.secrets.wireguard-home-preshared-key.path;
# name = "moonside";
# persistentKeepalive = 25;
# # endpoint = "${config.repo.secrets.common.ipv4}:51820";
# endpoint = "${config.repo.secrets.common.wireguardEndpoint}";
# # allowedIPs = [
# # "192.168.3.0/24"
# # "192.168.1.0/24"
# # ];
# allowedIPs = [
# "192.168.178.0/24"
# ];
# }
# ];
# };
# };
# };
# };
}; };
} }

View file

@ -8,9 +8,9 @@
config.swarselsystems.proxyHost != config.node.name config.swarselsystems.proxyHost != config.node.name
then then
if if
config.swarselsystems.server.wireguard.isClient config.swarselsystems.server.wireguard.interfaces.wgProxy.isClient
then then
globals.networks."${config.swarselsystems.server.wireguard.serverNetConfigPrefix}-wg".hosts.${config.node.name}.ipv4 globals.networks."${config.swarselsystems.server.wireguard.interfaces.wgProxy.serverNetConfigPrefix}-wgProxy".hosts.${config.node.name}.ipv4
else else
globals.networks.${config.swarselsystems.server.netConfigName}.hosts.${config.node.name}.ipv4 globals.networks.${config.swarselsystems.server.netConfigName}.hosts.${config.node.name}.ipv4
else else

View file

@ -1,9 +1,9 @@
{ self, homeConfig, lib, pkgs, ... }: { self, homeConfig, lib, pkgs, config, ... }:
let let
mkPackages = names: pkgs: builtins.listToAttrs (map mkPackages = names: pkgs: builtins.listToAttrs (map
(name: { (name: {
inherit name; inherit name;
value = pkgs.callPackage "${self}/pkgs/config/${name}" { inherit self name homeConfig; }; value = pkgs.callPackage "${self}/pkgs/config/${name}" { inherit self name homeConfig config; };
}) })
names); names);
packageNames = lib.swarselsystems.readNix "pkgs/config"; packageNames = lib.swarselsystems.readNix "pkgs/config";

View file

@ -0,0 +1,9 @@
{ name, writeShellApplication, config, ... }:
writeShellApplication {
inherit name;
runtimeInputs = [ config.nix.package ];
text = ''
sudo nix-env --list-generations --profile /nix/var/nix/profiles/system
'';
}

View file

@ -0,0 +1,9 @@
{ name, writeShellApplication, config, ... }:
writeShellApplication {
inherit name;
runtimeInputs = [ config.nix.package ];
text = ''
sudo nix-env --switch-generation "$1" -p /nix/var/nix/profiles/system && sudo /nix/var/nix/profiles/system/bin/switch-to-configuration switch
'';
}

View file

@ -5,6 +5,7 @@
swarselmodules = { swarselmodules = {
anki = lib.mkDefault true; anki = lib.mkDefault true;
anki-tray = lib.mkDefault true; anki-tray = lib.mkDefault true;
attic-store-push = lib.mkDefault true;
atuin = lib.mkDefault true; atuin = lib.mkDefault true;
autotiling = lib.mkDefault true; autotiling = lib.mkDefault true;
batsignal = lib.mkDefault true; batsignal = lib.mkDefault true;

View file

@ -20,7 +20,7 @@
diskEncryption = lib.mkDefault true; diskEncryption = lib.mkDefault true;
packages = lib.mkDefault true; packages = lib.mkDefault true;
ssh = lib.mkDefault true; ssh = lib.mkDefault true;
nginx = lib.mkDefault true; attic-setup = lib.mkDefault true;
}; };
}; };
}; };

32
secrets/nginx/acme.json Normal file
View file

@ -0,0 +1,32 @@
{
"swarsel.win": {
"fulldomain": "ENC[AES256_GCM,data:CVasUSMRn/KWzVRlcYfTO/RL+W5Cz2JpDj0JLAKITXrDZrl+Wsg46X8zv4hX6NLj/wAyvXQ=,iv:N3DL4JPX8vWTbllFWcpNulwtDJ57xpHrAwoUxWhTzxs=,tag:CYWoK9uT121rFXQ5h69CZA==,type:str]",
"subdomain": "ENC[AES256_GCM,data:uM457vEJa10IV4SovBDUzLLlW+mPwh1SiWr8thQisFoe6zAk,iv:Tdbd5a20Gv/thkPfsvNiAbI86JjcDs70MAfk4yCZLgs=,tag:MulJiRWPs215x0bc+1jBiA==,type:str]",
"username": "ENC[AES256_GCM,data:ePE2BEKL5uaXqzGngW9ArhwP3qwDzwULtfwUfb5Q56VGGURp,iv:/GZRbyXHorcq1PIYlhfOmUVwCg0I/N4ZraEzSrc8qmA=,tag:wM5B1U0BsRsBAJg3qNOXpA==,type:str]",
"password": "ENC[AES256_GCM,data:RGzdi8IMqm+rtiuU4RtWGQ4N/7FYBbp5Pir8/k2V1QEdM8z7SIn0FQ==,iv:ThFbY9eZuEZoyzcWV5DwtSi8ugNwM49JfRof560Qx/Y=,tag:sgMaLrPB8WgpXWPzaCwOBQ==,type:str]",
"server_url": "ENC[AES256_GCM,data:zJdXoO7ED7qeskYJ9Wu0Rdprbvj/uP+Z,iv:ce+QXocqCjNKCsZRyVt6koUyc2lsTwPNMcfQyqbktN0=,tag:bQSE4/6va+V0TORWANLdUA==,type:str]"
},
"sops": {
"age": [
{
"recipient": "age1g7atkxdlt4ymeh7v7aa2yzr2hq2qkvzrc4r49ugttm3n582ymv9qrmpk8d",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBBWmFOUGZrbkNlQ2ppbnFw\nTVd3RVYrK1RocjU3Z0pPOUNpc05YZ0FKK2hJClAvdkZ0aFRtaE5PRWFNbWdWSjN3\nbWc1WUpuTTVDTlNldTh0ejBGakZvbDgKLS0tIHJaRmwwSEtqQXprcFZ3TlllWmJa\nczN6eE8wWUppc3o1VTRpcjF0VUM1azQKdWhbP34mx6yR6TXUMpP/npA9TjAayjqz\nHC7ztK5/zu4ML7BRCsoLNLDDVtMsmhEVKO8VV9sbMvusSq0WNu0I7g==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1cmzh82q8k59yzceuuy2epmqu22g7m84gqvq056mhgehwpmvjadfsc3glc8",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAxVUM5eTMrZ3lCeDgxVHlj\naWx4QUZ5c0NGV2xEaXliNHFyVktkTnBYR0d3CnRMcUgyUU1hNzhLcWhaYzJCMndR\nQWJyNEZJV2xNVVBSc2xnbzRqdEx1eXMKLS0tIDJhTGhVYkxJcGZ6N3dHZmV3QlBo\nNzBORDJXb1pRbVNiWHZiVjBKQjNwWDAK1z/XA6gRor2fNX70UooBqHjCFfIIP34j\n+vHx10dtoue5tSPgM3cA8QZzqM6Ht+U282JFMztrlDtiz/j/JofTNg==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2025-12-04T19:04:02Z",
"mac": "ENC[AES256_GCM,data:nWV/knCo/MeWTBrfq1VlV6SPEQ2i2P+le82S2So0BIxPfz8tqan0MdaIaKLFlapsT9VRJOv8ZCCXSLWeGcbEvfmEz4MP1E4iHcU/4YaO+n895D1JrjeyP1cgGisnXqe01xMXCsDY178sqxHcnDDlXp9foCem+mGjIlKGPYGu5Oo=,iv:qbavbW3MF4fx+E3aybBYaz/T/Hb63ggWml4Oe9WFz+I=,tag:05vBbBGDGRNaXJWoZn1bVw==,type:str]",
"pgp": [
{
"created_at": "2025-12-22T14:07:12Z",
"enc": "-----BEGIN PGP MESSAGE-----\n\nhQIMAwDh3VI7VctTAQ//XZNxhn6vCEDo6SAtlIcA5OxNuj3326ITbjjv+XwiTHk/\n28cErtXOF+4bnjIXLwXGbbewWgvrnK0Lx5nBqpidMrhANHVEWDp3iBrW79QPdRl9\ntIcofQUK+6Dwh+XqJK4eorEAeAKlu6l5y3pc19L4j7BiDOxcXBYqTx4ScnZhFFoc\nRVC6ngRjck+2e6bL/+lopntHML41yrXpAHMBlobTbzYP8cnfMKroUZzLoNareVIS\nNlcE7oaCwAvCXYBbQN5qQCIFbLBFB+vD4ArOrL/zNxWVjnEeM4RhBdIjd0bdq1FQ\nLBsO1crEniz4Lgp5N4cWOzuOCz5fHlLJEKtNIIFt8rcocBEdyQf1OmZ2VrY57nN+\nSeCYGN+fPA87UoH+iZcmLNjDvMy+MHNIiGCEuY9jWLzgcDDvtFwtQdl2CpI8a/Ax\nKG9RgMFyjLtPCgNzDkn6LrvHKtnFkcsx22f8PHB4RZIgP43loH//xieOVnA9vo/k\nV3pRHZuZkTp3qmAED3KhbjE7aKP3bjUkJdrTATsal5YyRd76pjPvtXkX6MchgTgh\n6emt6DssvWJy5vhgitM0Ucq1IeeaLwIDTXqP5GShcQrWJ2P5ilzLGp2OKYzOqSBf\n//LfA3EHq4aFaTGuOVxjf/VS1vEeIAMaAo4eo86unIIhcQ6OBVAKzKfrSgzV9iiF\nAgwDC9FRLmchgYQBEACFwFYn12YCzYIwpSfJ7Apo5nEiLYbcD1wXR7bx7MFjJFxc\ncrS6yB/44y6a+OJfTLEVpDTPahKiOFAlmMAb8B05bDd/jqairwJPS+Sw2t/Tb6Ry\nToHi8ucg0+P8b0j+VeXoE0DlnuSMKbKQGvIq9fd8G6FymHppA4TpuKFl0QPUAbX7\nGLQbApk7gRoZK1XTQ/HKjyXdhfRogo8/07oRt6iO3nxC5NkLS/uxk/0ODqiFTGVg\nESkGQK3YQcr8hhhM467S49nzh+AQODgCTm7jSFO8kJwDP6eb5vF63qrIBZNUGc6j\nIoNHzQxqDoaFUd2GZcKg2Zsyizb91qYEMZdHUh51h1XH+jhg8G+L7I+hnBbzIj/o\naZfXALcmYNiI630SsiqA4iSufWy1bONnP0kHPSxVBqilLXpmfzIuNUwrZSQB+ZbR\nkIIA5lRqu1jWIcG/3CGQQMDfjqxus4Wt8bvGfOrbQk2qNs+l3uPb0+OBDNKQfVHD\nenXkTGrKIHt4YqnDc3ziWNAULwvRPviPQZPkJwPCDHdfA2bcQqLJOIYiBF1TdN9o\nv2eMQoTalEXfHOI5ndULjlyy5DaW2ZBKAgtH/WXg/O7ey5Q9e9QcnmwbOezEWx6x\nNNY3eoQs5LYxoWO7cEUv2MiEqY7ZWkrwmwcWzRV/7UmspvPHKULQV/fiNWb1yNJc\nATKv+sla0n0DzD+7BSiFcYC0ZvGAErYAwNIYhlgMEN7okuCscB+tz1d0yLhNt8DK\nZAn0NkIVnTn6esHqhp233ZmCrCmUezccMMINs8xQbqJR8bkns5hTDYLY48s=\n=v2/q\n-----END PGP MESSAGE-----",
"fp": "4BE7925262289B476DBBC17B76FD3810215AE097"
}
],
"unencrypted_suffix": "_unencrypted",
"version": "3.11.0"
}
}

View file

@ -0,0 +1 @@
M5vKsS+gw13pS7TTMktxBXJRooIMKiTXjZ6NJgQDAEY=

View file

@ -31,6 +31,8 @@ github-nixpkgs-review-token: ENC[AES256_GCM,data:/4ssZAEwEc9fZeR69GCvLMm4eRv4uab
#ENC[AES256_GCM,data:PI5MX6PgK1y0lqyoYA0=,iv:25UAvFaANHFD04GRafGlCzOc5h+15YPtSES2z2tmpXw=,tag:+XLwQ01+AtGWjtsSQhQ1AQ==,type:comment] #ENC[AES256_GCM,data:PI5MX6PgK1y0lqyoYA0=,iv:25UAvFaANHFD04GRafGlCzOc5h+15YPtSES2z2tmpXw=,tag:+XLwQ01+AtGWjtsSQhQ1AQ==,type:comment]
anki-user: ENC[AES256_GCM,data:WoGaNDAHFw==,iv:ZSjHfKMIjlgOuvGl7hVxJc1fE80nfxxXYLgsKangBCs=,tag:UP8ZI7gzOrJJjNDHovIkyg==,type:str] anki-user: ENC[AES256_GCM,data:WoGaNDAHFw==,iv:ZSjHfKMIjlgOuvGl7hVxJc1fE80nfxxXYLgsKangBCs=,tag:UP8ZI7gzOrJJjNDHovIkyg==,type:str]
anki-pw: ENC[AES256_GCM,data:z2SCsSvZIqN2/2VK1EdmcAnl42x5A15PAiK932k3n50Vj1jczGRoSw==,iv:keQCutY4vizVzu5YzPBJLgDLveYDb2VGeEnYmO7CeQw=,tag:KGplFfC5xktNAOTbIlt+Tg==,type:str] anki-pw: ENC[AES256_GCM,data:z2SCsSvZIqN2/2VK1EdmcAnl42x5A15PAiK932k3n50Vj1jczGRoSw==,iv:keQCutY4vizVzu5YzPBJLgDLveYDb2VGeEnYmO7CeQw=,tag:KGplFfC5xktNAOTbIlt+Tg==,type:str]
#ENC[AES256_GCM,data:veUC1sj6BSqHBA==,iv:L36lv9aQ38/WEaIccQDgOw2PB9U9k/t8x00wIw2Y858=,tag:3s2LBCwGzYpUk8WBj70UGQ==,type:comment]
attic-cache-key: ENC[AES256_GCM,data:2Xw8YX6wiQg2yb2pbZ/UowmzUdhtb2iRTVZZD2ypGaiwhI3mteG3qUgQm1oCz0bp+5jip6+kVzt576qVbUGim/m+dUZYU6mqm64/78bfuTvd/UBlJnmjNtWE2ILjnP+M4EodzbYlBlxwGhFS28wrVOHo77rzbcrPJEwZiqIzSgGIWKdNzzo5AXL2b1lKAngXO6Bi5Jc9W4lkTVFJ/Ixh6aOoHpq9TzsHHx2Aak22969pnxmFFpXKof4eiNGnoGBZDAr8pC7oSwVqDYbZwxH1ulRq863KVQkve+HBR2JJLAQjYHHUJJGhJG9jWYT03WjBNHwIDMTTvC9Fiw9Cr0TG0B8Bxwm3dhgLirjUyLOiST2CbDxxld1M8DJFkBwrih6hMJXmJw8Dlqy/D+3EZXT947BI8ythYjuL3jIHHQhUjfEf+sLdqPSngHolAAKqKE84Xv2FDn2wXGwe8UY3NMmIeaWYZsyDu77KnQR2R+6TuJTOw6vOdDoUJ55YRPdb9UR186b+TiSrP0SZOujoSYGs9dattEvN3XKlm3cQztB9UygmdEk/stDZ/CJIRUNXsu46o1nR5FWPkgoW91Fzxs00QgQMpYlnXM2CWknYMSHL45t0BYA7yuFwq9MYNUK/vrdCr3mtHxA6R28HajDUWoZA6uS+DF/i1nF79sYfam7SdKNCqu2r/1CGLblHQwKT27HmrTCXdjeLqe+Yv7sJzlEbV+sKD+ccW8jI4NZRjCbVJVKydK23YWj94NEt/M2rtxzV30XKw8GClqsdEF+v4nu48oB894RPZCy9qQjaFHnqYpiqSa0oXluiQQmRfA0jtQLRTXN5ri7U/GtfH1za179MFWwMorRMK6qdTt3pi8Fie4UgzGyGq6CugN8HxeMNl70pPVIKjGNO8Npezk6T3YDUpB3/OGY56jhSYxIEadBvW9CqDS7al7zEKgD1wx1gzT2mQh60H2B/InWg9p96qOqVEQxOFDklxlcnygLu3z7Y0mAds/HXOJJnJbagjfxVi+qROOtVrR5y/kySR0pM9Syk8GvqdtRct7qorONAV/yonarEgz+eEFj10kderSsPdz1sgiYe93VLmPp07cdVsUsaDtLW8gXafc3aWOZ8JIkSUhYDbR49pf2bTeoMDoyi9d6pgLr+cJGQbJC/1LmsAIqOQ7WPiTeAZG2lStNf3bwClpUuL0t78UabZyNzJJN5TFDZqGkwXlaJmQ==,iv:6sa44WnyrXW3KQHdGIKuiGWwqp3qtQu4Q9RSXA45PYs=,tag:MbtS4Xx5K8O3mFAlriuuIA==,type:str]
#ENC[AES256_GCM,data:KCqwghIJ8tlGFxMt94svo6285cA1YRbYoeivx6A=,iv:qlZCGrCn5fU1xPQF9wfOMarU6Z7oa3mLtd1LzVzMbuI=,tag:Qq5lBtUsd3lQMx6ffk+kzQ==,type:comment] #ENC[AES256_GCM,data:KCqwghIJ8tlGFxMt94svo6285cA1YRbYoeivx6A=,iv:qlZCGrCn5fU1xPQF9wfOMarU6Z7oa3mLtd1LzVzMbuI=,tag:Qq5lBtUsd3lQMx6ffk+kzQ==,type:comment]
builder-key: ENC[AES256_GCM,data:OOoA7oRIFJwS48qs42WmIXU4vLTQLRi6Nzb6IUNXAwnj7E9Q/uzOnmJ6ZihqUr3uP3/kqICdZqkB5Lf+lwUqrPmGi8PH8c4uq3L9ZVQyRTKRWvS5biYRMd/NI1ACRevxFJygKZ/q1dH8Cf++m26Jo9On24liepQbogwIctmN8bLXuXrUYVYMcLyGb2W2uznj5Jtwyji87S1xc/TTh/IOFexPY7yAnlwSZ7IoPTOj8o/00Hny8KIwRVf0uLBmtarIS8u4kfjkFUN38X9xmzQqhITaZNRmwerb3JDbD3SYapfsyeLzkzVgsDooOniCRehX1pg7KJu/qHnFM8t7e4qZe9u4PZP08aVAS3tqChuG/Nm4fv3vnF0SwctGQsar2HDmby1IXAw3fSNCW/v/l8VY4IaRX9s7e6c95gd5NX7/CJ2CJUDb4s3e7U0SkftOUz/B1zX47xBHSibZ2Js24N6Tvq/5S4OJFwryvt57Ed+cY+zSROlJwjrTgaGcuwH3MHLfHJlA,iv:2RpiHF4b7+520UJcHVobfJs165EjgxaTATSyOx7HJik=,tag:tGddPi0YeO3E0kHl+E7uGA==,type:str] builder-key: ENC[AES256_GCM,data:OOoA7oRIFJwS48qs42WmIXU4vLTQLRi6Nzb6IUNXAwnj7E9Q/uzOnmJ6ZihqUr3uP3/kqICdZqkB5Lf+lwUqrPmGi8PH8c4uq3L9ZVQyRTKRWvS5biYRMd/NI1ACRevxFJygKZ/q1dH8Cf++m26Jo9On24liepQbogwIctmN8bLXuXrUYVYMcLyGb2W2uznj5Jtwyji87S1xc/TTh/IOFexPY7yAnlwSZ7IoPTOj8o/00Hny8KIwRVf0uLBmtarIS8u4kfjkFUN38X9xmzQqhITaZNRmwerb3JDbD3SYapfsyeLzkzVgsDooOniCRehX1pg7KJu/qHnFM8t7e4qZe9u4PZP08aVAS3tqChuG/Nm4fv3vnF0SwctGQsar2HDmby1IXAw3fSNCW/v/l8VY4IaRX9s7e6c95gd5NX7/CJ2CJUDb4s3e7U0SkftOUz/B1zX47xBHSibZ2Js24N6Tvq/5S4OJFwryvt57Ed+cY+zSROlJwjrTgaGcuwH3MHLfHJlA,iv:2RpiHF4b7+520UJcHVobfJs165EjgxaTATSyOx7HJik=,tag:tGddPi0YeO3E0kHl+E7uGA==,type:str]
nixbuild-net-key: ENC[AES256_GCM,data:aAa6iyZsjH1sAb6ucSPJb2R+QiG2bTj46Csnjg58+2ngYdfuim6SzWEid8IHJV1+M0s/hVTbZWiPsU2KQ+JCdJ84as520avxs6I0URvNx+VmFi6DNGbBJJJJKdTXIKvtmLHqHobs9XtIHahQKoyUpXiSY88DcwAt4e2mUTa6olgrDv66+/fEGeexP4S7AVB0wYeegyMgWODRrA9gS/YLMxMdqk/VHuwIQpWkhxX+AY8mXkx7LalxrbtV/24qdNtr2GittrvYBAkYGWAVZYotBVKjaWVUVzqF3BU+wmg0c56OG5qtt9eD1THAqNauN3iIfUnV301S+TvVtYjpy7gOj3WzntO/kh4kD+7FnfleLXIVSLgBRc0vHhd+7HKKtVRnAINyPkyjaBpnnBa4cksBvHtI0uis0Pi+4JtObD3m+5dywTeL+HDQfHwu+7CgjvXHQYKvEaJ8z6alyXL88Q6uT2Ikaoyrkpi7OJsuIBiNbs5YzRReSfLVyepm8SAtA8UIwnMiTtgFvwGUEW19ne96,iv:2HN9X9CA1liWuY+LYqTCX6Zy3xARMS/TOL61r2UKsE8=,tag:XcPBwYrQjqhexI7u+0zXQw==,type:str] nixbuild-net-key: ENC[AES256_GCM,data:aAa6iyZsjH1sAb6ucSPJb2R+QiG2bTj46Csnjg58+2ngYdfuim6SzWEid8IHJV1+M0s/hVTbZWiPsU2KQ+JCdJ84as520avxs6I0URvNx+VmFi6DNGbBJJJJKdTXIKvtmLHqHobs9XtIHahQKoyUpXiSY88DcwAt4e2mUTa6olgrDv66+/fEGeexP4S7AVB0wYeegyMgWODRrA9gS/YLMxMdqk/VHuwIQpWkhxX+AY8mXkx7LalxrbtV/24qdNtr2GittrvYBAkYGWAVZYotBVKjaWVUVzqF3BU+wmg0c56OG5qtt9eD1THAqNauN3iIfUnV301S+TvVtYjpy7gOj3WzntO/kh4kD+7FnfleLXIVSLgBRc0vHhd+7HKKtVRnAINyPkyjaBpnnBa4cksBvHtI0uis0Pi+4JtObD3m+5dywTeL+HDQfHwu+7CgjvXHQYKvEaJ8z6alyXL88Q6uT2Ikaoyrkpi7OJsuIBiNbs5YzRReSfLVyepm8SAtA8UIwnMiTtgFvwGUEW19ne96,iv:2HN9X9CA1liWuY+LYqTCX6Zy3xARMS/TOL61r2UKsE8=,tag:XcPBwYrQjqhexI7u+0zXQw==,type:str]
@ -144,8 +146,8 @@ sops:
L0gzWDFia2Jha0lDaGZaSWEwOWFmb1EKJqqjxODIgVeiMKtV6361sjYQa559pKCG L0gzWDFia2Jha0lDaGZaSWEwOWFmb1EKJqqjxODIgVeiMKtV6361sjYQa559pKCG
1pKczlzXxL1FliBQoZZGq55NR4azWYEl/yV5tee1dtUohJW0pAyScA== 1pKczlzXxL1FliBQoZZGq55NR4azWYEl/yV5tee1dtUohJW0pAyScA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2025-12-01T19:57:43Z" lastmodified: "2025-12-23T01:11:36Z"
mac: ENC[AES256_GCM,data:2CLFlduO1fsxtvF1fbH18kadQuawMwIYEjsJBvZ65tecIdjT5efPD07+czmysKWBh6FQuVPL8a3uVlqT2WUW57AjQZtxloCMAFS9m2S//I6I8GsLVccGnmudiHUdXFnt+gI1gtb6ukZMEps4m/LSqUHGSptVwqrIN2gBM6Yy9Mo=,iv:S/crBYhr2HTzMYn83bK2YYO7kwfDspF0gvkoiuI9J7o=,tag:+sO+jFMFGZSsCb7PGnlUmw==,type:str] mac: ENC[AES256_GCM,data:e0WoFBQSR5q3GOQ+GMJGBd4lNBAMqlnVjtUq3snxrdvcytb9YvKnoYQH+GjbdGIiqrND8pOVnZt34AjkR8YfpWe+VrkP3Vj/3l+1GjF1XIHbzBNKOQHdYPSVsH2NZwftcAdphbStf3GTlb+b+cpTn4a9Y4pTNGVoOaOA1tBr8bM=,iv:sPXktitTNMkBhHr6E/QRZCVKrgyED9/o9hiivbObACI=,tag:tTNr4UEf92UrtI0Jvi5o3g==,type:str]
pgp: pgp:
- created_at: "2025-12-15T21:53:36Z" - created_at: "2025-12-15T21:53:36Z"
enc: |- enc: |-

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,8 @@
wireguard-twothreetunnel-winters-presharedKey: ENC[AES256_GCM,data:v+RyEcJh7dSBuOUgJFG6f3C8CYchEDy1mk2vUXlJbeeqAFcU3d9/M7QbHz0=,iv:GGJ61LsTq1lKcg0xjO3Co1PVqGW56Tgb0dWtT+7suz8=,tag:WCAsVbBsOeo7xmqRwdCinA==,type:str] wireguard-twothreetunnel-winters-wgProxy-presharedKey: ENC[AES256_GCM,data:FZWARdSb8o3KpgJikYIC7hYLZ+WUaulZoisE4Ifdpsr8tBazY9bVlf6D9p4=,iv:QSUUWFR6uUoX62l0kMiVKkmlI0lEXuDEV7PSfBLJymM=,tag:eupex55cEm7lDKHFbJOkmg==,type:str]
wireguard-twothreetunnel-moonside-presharedKey: ENC[AES256_GCM,data:vkUcgip1lrYEYwcbLF5WSPUK8m7ouuyDtVykcl4Lah21asBhCmDtqqNzvvw=,iv:1zVhRsxLQvBpHcSjcswwSCotelZk7SWI9NkXdrAS21Y=,tag:0DvNJMaiYl93flbjNQBVwg==,type:str] wireguard-twothreetunnel-moonside-wgProxy-presharedKey: ENC[AES256_GCM,data:Y/EwbaVbGljiz9XZmr7+/udBfeaY+CLMfnKzekXP50Hu8ek8aA1/SKs2qd0=,iv:BijEDkfpRWox8CPwCoZLA42WihYIqHJJgSgOfsOGcG4=,tag:8+qEnqTsqyNdD4oVPiuQuQ==,type:str]
wireguard-twothreetunnel-eagleland-presharedKey: ENC[AES256_GCM,data:5JMTgXPcRzcr3GfGinAeLvldweArGyB8gyqRNtxlKa1RPY9pPa7s8Pxhdng=,iv:Mk6fLKpf8XbSGEU8b/j+ZZcQxcxhKGHRPSmk5Q9lrXQ=,tag:GACQf8x46LVQMwrlp6+NSA==,type:str] wireguard-twothreetunnel-eagleland-wgProxy-presharedKey: ENC[AES256_GCM,data:dF8VPApd6iYKIZjBXB2rjIXIxyy2+U76TdyFuyUW0zSbtjzqn1ZKrhX4w/M=,iv:GqOHsS97di9sHqjndlq0EdWLcJ1EMLmDOnFJlBgTvYU=,tag:PdxEYlg3lPShUJYlANLjhg==,type:str]
wireguard-twothreetunnel-belchsfactory-presharedKey: ENC[AES256_GCM,data:MKla1VJiFzpWxrxXA+FUU10KQmrO926wKdBCM0tQDvkQWrR1FpvbbeBfdzM=,iv:5aIhbhiAsfIXUojuLwRsuhZAPvGkORZTOo+0Rc5/bpY=,tag:nG++1kDULmAMfCx0a8P40Q==,type:str] wireguard-twothreetunnel-belchsfactory-wgProxy-presharedKey: ENC[AES256_GCM,data:NAbVE7ysGDD6TT0RxdL6bTNloac4RBU1JWeTFqYo9PO6ZU2f/yq6aboi2AA=,iv:Ky4UvgRDEG1UgDmi+m5mHWHO+yUGzphQPYIuyAXDkhw=,tag:WP+/8q8jfitNC/rXN5Mp2A==,type:str]
wireguard-hintbooth-winters-wgHome-presharedKey: ENC[AES256_GCM,data:57KGUxn1BibZ+9H9mXg9EYmcy1JBX+M79ACL3Qt0XEMl0dFlk9Wq6cr3JTg=,iv:9QHdykNlUU1H0uco21zA8leQH73PAeL+xTVi6V9zx7U=,tag:6mfSCddnVGMBEAqCHDIIrw==,type:str]
sops: sops:
age: age:
- recipient: age1s0vssf9fey2l456hucppzx2x58xep279nsdcglvkqm30sr9ht37s8rvpza - recipient: age1s0vssf9fey2l456hucppzx2x58xep279nsdcglvkqm30sr9ht37s8rvpza
@ -112,8 +113,8 @@ sops:
THZaSXhMNldUbHpBMVZQWXYwaVVQYWsKpUbG+lC37W6bzOuu9MaUmEZ5T1b5EC9k THZaSXhMNldUbHpBMVZQWXYwaVVQYWsKpUbG+lC37W6bzOuu9MaUmEZ5T1b5EC9k
VIY9XUoA7h0Z6G5Jrx/lrf6qsghMqd59gPA1qh8KlCJBAUJPHzQKVg== VIY9XUoA7h0Z6G5Jrx/lrf6qsghMqd59gPA1qh8KlCJBAUJPHzQKVg==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2025-12-03T15:54:14Z" lastmodified: "2025-12-22T09:11:50Z"
mac: ENC[AES256_GCM,data:yPPp6WQvcMB6mV/V62Y0Ki8Fq2trV3HKTykP5HD7J7ZgUiFNmLPbAApMeefe7Bb8pF7ETrkjRhET7/pXJX7anaOd6Y6pCyF75/xQDzwJ0Ac3FSPgeoOwlA/W+OrRMm9Hla9Q5gNPzgYirIHXoO+aScLcTQp46tiQv8B0Ol0ZSoI=,iv:eIT/EcVpWk79eerrxqy7AqtqNtpJi5sOIe0wmHRjYfI=,tag:4KPyZE9s62fCisjgihH4Jg==,type:str] mac: ENC[AES256_GCM,data:D6qKXhSuYGPm9K7Al/la3O++DmOTQN5++96k44IIvgSR5Q3kTkMYxPsf3PWNyjMm09+9aRauuHPHj098+W1rXaq43Iqr24JAuaZNwLyxtqkaibv/Zhx1RgEWGXyOHDtlfIPoULNKVZ0ls2mtk40oQHsfhnRyS42m+HMQnL+tCF4=,iv:uzKbpZu9P05KbaXnBUxN4rA9nYOXpeK+E/scWoFxpcs=,tag:PuMdBx3JOT1EfFkOPV0G2g==,type:str]
pgp: pgp:
- created_at: "2025-12-15T21:53:40Z" - created_at: "2025-12-15T21:53:40Z"
enc: |- enc: |-