diff options
Diffstat (limited to '')
184 files changed, 8406 insertions, 3752 deletions
diff --git a/.envrc b/.envrc index 19a0dd1..71bc7ad 100644 --- a/.envrc +++ b/.envrc @@ -1,7 +1,9 @@ #! /usr/bin/env sh use flake + +PATH_add ./scripts/ + if on_git_branch; then - echo && git status --short --branch && - echo && git fetch --verbose + echo && git status --short --branch fi diff --git a/README.md b/README.md new file mode 100644 index 0000000..4f2fab5 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# Vhack.eu server infrastructure + +This repository includes the all configurations for all services at vhack.eu and +foss-syndicate.org. + +## Contributing + +### Issue reporting + +Issues are tracked [online](https://issues.foss-syndicate.org/issues/open) by +[back](./pkgs/by-name/ba/back). + +Please send issue reports via mail to [admin@vhack.eu](mailto://admin@vhack.eu). Beware +that we use [git-bug](https://github.com/git-bug/git-bug) for issue tracking, so you can +clone this repository and get a list of open issues with `git bug ls`. +We plan to improve this issue reporting system in the future. + +### Patches + +Please send your patches for now to [admin@vhack.eu](mailto://admin@vhack.eu) (with `git format-patch` and `git send-email`). +We're planning to set up a mailing list in the future to improve this process. diff --git a/flake.lock b/flake.lock index 1bc5e2a..a23f22a 100644 --- a/flake.lock +++ b/flake.lock @@ -12,11 +12,11 @@ ] }, "locked": { - "lastModified": 1723293904, - "narHash": "sha256-b+uqzj+Wa6xgMS9aNbX4I+sXeb5biPDi39VgvSFqFvU=", + "lastModified": 1736955230, + "narHash": "sha256-uenf8fv2eG5bKM8C/UvFaiJMZ4IpUFaQxk9OH5t/1gA=", "owner": "ryantm", "repo": "agenix", - "rev": "f6291c5935fdc4e0bef208cfc0dcab7e3f7a1c41", + "rev": "e600439ec4c273cf11e06fe4d9d906fb98fa097c", "type": "github" }, "original": { @@ -43,11 +43,11 @@ }, "crane": { "locked": { - "lastModified": 1734541973, - "narHash": "sha256-1wIgLmhvtfxbJVnhFHUYhPqL3gpLn5JhiS4maaD9RRk=", + "lastModified": 1742394900, + "narHash": "sha256-vVOAp9ahvnU+fQoKd4SEXB2JG2wbENkpqcwlkIXgUC0=", "owner": "ipetkov", "repo": "crane", - "rev": "fdd502f921936105869eba53db6593fc2a424c16", + "rev": "70947c1908108c0c551ddfd73d4f750ff2ea67cd", "type": "github" }, "original": { @@ -111,11 +111,11 @@ ] }, "locked": { - "lastModified": 1734343412, - "narHash": "sha256-b7G8oFp0Nj01BYUJ6ENC9Qf/HsYAIZvN9k/p0Kg/PFU=", + "lastModified": 1741786315, + "narHash": "sha256-VT65AE2syHVj6v/DGB496bqBnu1PXrrzwlw07/Zpllc=", "owner": "nix-community", "repo": "disko", - "rev": "a08bfe06b39e94eec98dd089a2c1b18af01fef19", + "rev": "0d8c6ad4a43906d14abd5c60e0ffe7b587b213de", "type": "github" }, "original": { @@ -183,11 +183,11 @@ }, "impermanence": { "locked": { - "lastModified": 1734200366, - "narHash": "sha256-0NursoP4BUdnc+wy+Mq3icHkXu/RgP1Sjo0MJxV2+Dw=", + "lastModified": 1737831083, + "narHash": "sha256-LJggUHbpyeDvNagTUrdhe/pRVp4pnS6wVKALS782gRI=", "owner": "nix-community", "repo": "impermanence", - "rev": "c6323585fa0035d780e3d8906eb1b24b65d19a48", + "rev": "4b3e914cdf97a5b536a889e939fb2fd2b043a170", "type": "github" }, "original": { @@ -198,27 +198,27 @@ }, "library": { "locked": { - "lastModified": 1735055361, - "narHash": "sha256-wZmUlcUG6ktcMuI3DVO2HsnpqX7z5iLdMwOo0YgVdGM=", + "lastModified": 1738443114, + "narHash": "sha256-IV7n/l3rFoz5UuavrDv0a7IIOPne0jDQVmJAR8bve8U=", "ref": "prime", - "rev": "10c82665cb197b68ff0d9bb02e12a4287f1b8925", - "revCount": 2, + "rev": "65bf71bb6ef05ce684924a1dc248bb2e8e2869fb", + "revCount": 17, "type": "git", - "url": "https://git.vhack.eu/vhack.eu/nix-library" + "url": "https://git.foss-syndicate.org/vhack.eu/nix-library" }, "original": { "ref": "prime", "type": "git", - "url": "https://git.vhack.eu/vhack.eu/nix-library" + "url": "https://git.foss-syndicate.org/vhack.eu/nix-library" } }, "nixpkgs": { "locked": { - "lastModified": 1734522913, - "narHash": "sha256-tyReZKZRdyODkbcwYnO7xowXx7VCFJ6XzAY7w2aFjs0=", + "lastModified": 1743036386, + "narHash": "sha256-W1Qap/jwnpvWPXC+cUp0PlaZsJO05sfQfzffRrYW7YY=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "bf383789c497270e8e20ccc2261cf2c6e18dbda8", + "rev": "1751c9cb80247edc5fed79b90211a92c56bf91e6", "type": "github" }, "original": { @@ -228,28 +228,28 @@ "type": "github" } }, - "nixpkgs-24_05": { + "nixpkgs-24_11": { "locked": { - "lastModified": 1731797254, - "narHash": "sha256-df3dJApLPhd11AlueuoN0Q4fHo/hagP75LlM5K1sz9g=", + "lastModified": 1734083684, + "narHash": "sha256-5fNndbndxSx5d+C/D0p/VF32xDiJCJzyOqorOYW4JEo=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "e8c38b73aeb218e27163376a2d617e61a2ad9b59", + "rev": "314e12ba369ccdb9b352a4db26ff419f7c49fa84", "type": "github" }, "original": { "id": "nixpkgs", - "ref": "nixos-24.05", + "ref": "nixos-24.11", "type": "indirect" } }, "nixpkgs-unstable": { "locked": { - "lastModified": 1734536697, - "narHash": "sha256-G/HnRTtU+ob8x967kjzMRqjNFbAdllrcjYc+IcaR15Y=", + "lastModified": 1743039536, + "narHash": "sha256-O3GFPU0Uyv80LKVMMukVqrfxSzWKkBwQIHN2UnRSCZk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9c40bef08a5bdc0ccc3207f4282a1ded83e77a7a", + "rev": "0ab4a506520ea9247e673e9849ecf84c72f88be2", "type": "github" }, "original": { @@ -278,11 +278,11 @@ ] }, "locked": { - "lastModified": 1726755133, - "narHash": "sha256-03XIEjHeZEjHXctsXYUB+ZLQmM0WuhR6qWQjwekFk/M=", + "lastModified": 1741508717, + "narHash": "sha256-iQf1WdNxaApOFHIx4RLMRZ4f8g+8Xp0Z1/E/Mz2rLxY=", "owner": "yaxitech", "repo": "ragenix", - "rev": "687ee92114bce9c4724376cf6b21235abe880bfa", + "rev": "2a2bea99d74927e54adf53cbf113219def67d5c9", "type": "github" }, "original": { @@ -317,11 +317,11 @@ ] }, "locked": { - "lastModified": 1734575524, - "narHash": "sha256-BxQ/4JuHEi0zRjF0P8B5xnbXOLulgsK2gfwVRXGZ4a4=", + "lastModified": 1743042789, + "narHash": "sha256-yPlxN0r3pQjUIwyX/qeWSTdpHjWy/AfmM0PK1bYkO18=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "573c674a3ad06e8a525263185ebef336a411d1d5", + "rev": "b4d2dee9d16e7725b71969f28862ded3a94a7934", "type": "github" }, "original": { @@ -339,14 +339,14 @@ "nixpkgs": [ "nixpkgs" ], - "nixpkgs-24_05": "nixpkgs-24_05" + "nixpkgs-24_11": "nixpkgs-24_11" }, "locked": { - "lastModified": 1734370678, - "narHash": "sha256-a8zkti1QM5Oxkdfnzr/NjrFlyqI36/kYV/X8G1jOmB4=", + "lastModified": 1742413977, + "narHash": "sha256-NkhM9GVu3HL+MiXtGD0TjuPCQ4GFVJPBZ8KyI2cFDGU=", "owner": "simple-nixos-mailserver", "repo": "nixos-mailserver", - "rev": "c43d8c4a3ce84a7bebd110b06e69365484db6208", + "rev": "b4fbffe79c00f19be94b86b4144ff67541613659", "type": "gitlab" }, "original": { @@ -378,11 +378,11 @@ ] }, "locked": { - "lastModified": 1734704479, - "narHash": "sha256-MMi74+WckoyEWBRcg/oaGRvXC9BVVxDZNRMpL+72wBI=", + "lastModified": 1742982148, + "narHash": "sha256-aRA6LSxjlbMI6MmMzi/M5WH/ynd8pK+vACD9za3MKLQ=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "65712f5af67234dad91a5a4baee986a8b62dbf8f", + "rev": "61c88349bf6dff49fa52d7dfc39b21026c2a8881", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 6ff085f..4d127f1 100644 --- a/flake.nix +++ b/flake.nix @@ -5,7 +5,7 @@ nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11-small"; nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable-small"; - library.url = "git+https://git.vhack.eu/vhack.eu/nix-library?ref=prime"; + library.url = "git+https://git.foss-syndicate.org/vhack.eu/nix-library?ref=prime"; treefmt-nix = { url = "github:numtide/treefmt-nix"; @@ -135,7 +135,7 @@ ]; tests = import ./tests {inherit pkgs specialArgs nixLib;}; - vhackPackages = import ./pkgs {inherit pkgs nixLib;}; + vhackPackages = import ./pkgs {inherit pkgs nixLib pkgsUnstable;}; hosts = import ./hosts {inherit pkgs nixLib nixpkgs specialArgs extraModules deployPackage;}; diff --git a/hosts/by-name/server1/configuration.nix b/hosts/by-name/server1/configuration.nix deleted file mode 100644 index 6bb1067..0000000 --- a/hosts/by-name/server1/configuration.nix +++ /dev/null @@ -1,46 +0,0 @@ -{config, ...}: { - imports = [ - ./networking.nix # network configuration that just works - ./hardware.nix - - ../../../system - ]; - - vhack = { - back = { - enable = true; - repositories = { - "${config.services.gitolite.dataDir}/vhack.eu/nixos-config.git" = { - domain = "issues.vhack.eu"; - port = 9220; - }; - }; - }; - etesync.enable = true; - git-server.enable = true; - nginx.enable = true; - nix-sync.enable = true; - openssh.enable = true; - peertube.enable = true; - postgresql.enable = true; - redlib.enable = true; - users.enable = true; - persist = { - enable = true; - directories = [ - "/var/log" - - # TODO(@bpeetz): Instead of persisting that, encode each uid/gid directly in the - # config. <2024-12-24> - "/var/lib/nixos" - ]; - }; - }; - - boot.tmp.cleanOnBoot = true; - zramSwap.enable = true; - networking.hostName = "server1"; - networking.domain = "vhack.eu"; - - system.stateVersion = "22.11"; -} diff --git a/hosts/by-name/server2/configuration.nix b/hosts/by-name/server2/configuration.nix index 07b78c3..a11085e 100644 --- a/hosts/by-name/server2/configuration.nix +++ b/hosts/by-name/server2/configuration.nix @@ -1,31 +1,79 @@ -{config, ...}: { +{ + config, + lib, + ... +}: { imports = [ ./networking.nix # network configuration that just works ./hardware.nix ]; + sils = { + gallery = { + enable = true; + domain = "gallery.s-schoeffel.de"; + }; + }; + vhack = { back = { enable = true; - repositories = { - "${config.services.gitolite.dataDir}/repositories/vhack.eu/nixos-server.git" = { - domain = "issues.foss-syndicate.org"; - port = 9220; - }; + domain = "issues.foss-syndicate.org"; + settings = { + scan_path = "${config.services.gitolite.dataDir}/repositories"; + project_list = "${config.services.gitolite.dataDir}/projects.list"; }; }; + backup = { + enable = true; + privateSshKey = ./secrets/backup/backupssh.age; + privatePassword = ./secrets/backup/backuppass.age; + user = "u384702-sub3"; + }; + dns = { + enable = true; + openFirewall = true; + interfaces = [ + "185.16.61.132" + "2a03:4000:a:106::1" + ]; + zones = import ../../../zones {inherit lib;}; + }; + etesync = { + enable = true; + secretFile = ./secrets/etesync/secret_file.age; + }; fail2ban.enable = true; git-server = { enable = true; domain = "git.foss-syndicate.org"; gitolite.adminPubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIME4ZVa+IoZf6T3U08JG93i6QIAJ4amm7mkBzO14JSkz cardno:000F_18F83532"; }; + invidious-router = { + enable = true; + domain = "invidious-router.vhack.eu"; + extraDomains = [ + "video.fosswelt.org" + "invidious-router.sils.li" + ]; + }; + mail = { + enable = true; + fqdn = "mail.foss-syndicate.org"; + }; + nextcloud = { + enable = true; + hostname = "nextcloud.vhack.eu"; + adminpassFile = ./secrets/nextcloud/adminpassFile.age; + }; nginx = { enable = true; redirects = { "source.foss-syndicate.org" = "https://git.foss-syndicate.org/vhack.eu/nixos-server"; + "source.vhack.eu" = "https://source.foss-syndicate.org"; }; }; + nixconfig.enable = true; openssh.enable = true; persist = { enable = true; @@ -33,6 +81,7 @@ "/var/log" ]; }; + redlib.enable = true; rust-motd.enable = true; users.enable = true; }; diff --git a/hosts/by-name/server2/secrets/backup/backuppass.age b/hosts/by-name/server2/secrets/backup/backuppass.age new file mode 100644 index 0000000..5fd5568 --- /dev/null +++ b/hosts/by-name/server2/secrets/backup/backuppass.age @@ -0,0 +1,14 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2R1JQczJDblhnWmZQMkJU +SVNwS2RNSkMwNHVGdHg4U1dsdXdXUTVOanlVCjNPQWxST2pnYXdIVjl1TExQNzlt +V0QwTzdWcTNJM0lJNW1OaExHcjlhWU0KLT4gWDI1NTE5IG10Y01KcDJWUUV5SVo2 +RmlMbHNWcS82enAvckZSWUVQbFdyMTdtY2NqR1kKbmVtSzRGYVdiTWdyMTA0SWQy +M1FYWTZidWI5UGIvVmxYbUphQkhJWUt4SQotPiBzc2gtZWQyNTUxOSBYUG94RFEg +WTd4ekxiWUR0WVoybU5VVy9TenpldDRMSTduQm5idzJZSWVCMHRlZmVEbwpqamps +Q2tuUHc0bU1kcHIvZ3FQalVMMWZ6aThsRDRNOHpUOTVGbkZ6TnR3Ci0+IDttZ2VJ +RzMtZ3JlYXNlIDFXIEpeIicqID1JLSFZaDcgd0ZzOjUKc3dCbDdjNmEzRUtjc0VN +SHM2MU4zVkFhQWdHd0JxVnpFVDN0UHpQYVE0d2s0QmQwbzRZZHpzanQzYnZRCi0t +LSBpR0E0V3FiV2pjVWt2OFY5UE1BQlpteXZWekZNK1lHSFV4TzFQVVV0em9RChir ++4/eHcBC2sNJgSssV4Zh/7p2GZrN7fyuxc29lhhGAQsRZ+VE9xSy08q2vIPRlqjf +nG72bAKGPiviFpH+uCWWllwoERST1QkkcqpyPjXzVpHrElSXHeE= +-----END AGE ENCRYPTED FILE----- diff --git a/hosts/by-name/server2/secrets/backup/backupssh.age b/hosts/by-name/server2/secrets/backup/backupssh.age new file mode 100644 index 0000000..c2d3abb --- /dev/null +++ b/hosts/by-name/server2/secrets/backup/backupssh.age @@ -0,0 +1,22 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjclNCOGsxNUNEWEJDSGpQ +MW8vc2FnakpTczhVbEFFenk5V2tSZm5IdGpvCkVzejlwT2svT2pLRExDbTdXajEy +elp5QTBTRGErL3NkRmJIU2lVNTI5V00KLT4gWDI1NTE5IFRjamhzdlhDUVl2RkhY +ZUhwTmg2V2NCeHFUb2hWdFMxL0czUWZteE5tSFkKaWNFa2NhdzQrZUNWMVFKRzNP +QVJzdEJZRXZlRUFQMTBscGZRNC83Rk55RQotPiBzc2gtZWQyNTUxOSBYUG94RFEg +R1JVdEU0SGJNak16ZmRaNzdlaTd5ZUdjUjYzZ3ljQ3J2cGkxUDV3TE93bwpQbDlE +SUFBblNvUmR4N09MUHFuamtiUVh0M244SGluZmFzenc0OS9uakNZCi0+IEwtZ3Jl +YXNlICp6IDp6OEJTIW43IHNaUih6YApuUmRZeWZwdFRCOTFTSXlMVkZxYW52azd4 +ZisrSmR6SEhJTWlGNWxtVzJBRWdmMnBhWVRuc1J0QUgxZ0lKZ0dLCm1KdklXL2xn +M3Y0NUVmeDhLWHRHWlhSbzhmNGNUU3R0OFdBCi0tLSB3UHphWkpuU1RENU16Nkln +V2k5TjRhejdCd2VCMXBaU0JSaEtuTmdvWTBnCpLTtP020Vy7Rldly79rARfETmam +kbRUCWiyHeKnFUWeraVr1R/l4Rt5QJh9Y6hxEBudymbyOy0VMZiQPZv7jq/pmDiB +ULnSnfRVZM7gmU09loxf9S4LatDT/Rjf/B8uMef7Ru89DH0fnewmSGcn0KkQMUNg ++ZNtg1Qti3R1baF7ZyXZfi1UY2oIbVe1T4iZQm7n0RdP/+taCm4EfNmX3QQely/R +CTRWl3An28JTWUePAO5qJWlvisRjNWFlsFGA+UZSRQVfWmiSnMlZ1PNbnNAo9+K4 +lIn2LNLZAOh0Cp+Rl38pusLlVLefyXhomdrp6vfE6mxBTk3scVfipDrChyt8jvbM +2CxUA2zhZ63kNDsQmrEbH375XKzOy2vIPMTzohQx3uN0fFBIQW9pPJcNCN7jJOQU +8CCL0R56Q5nQbNI+oz4oBuolhszkYPaiIzBlcHjjJUjxnUa5RX0SXTI7gCkqlIqZ +niS9z1Vql3QUTdPEyrhfzwOqDcGWr6B/edNHE6D5ILUm5mis/mJgRcEiF0Y/BlZi +mHPTGVdzkhtIIGEqiSlWMvB6zoL4uTru+yiB +-----END AGE ENCRYPTED FILE----- diff --git a/hosts/by-name/server2/secrets/etesync/secret_file.age b/hosts/by-name/server2/secrets/etesync/secret_file.age new file mode 100644 index 0000000..ac578a4 --- /dev/null +++ b/hosts/by-name/server2/secrets/etesync/secret_file.age @@ -0,0 +1,17 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBheEs5eGhURk1DY2RpczNV +RUNzREpUdFZpdXBzMlhMbW8zT3BGUzJMT0ZBClBqRUhEdU9VY1FzYkxnN3NpY3Ew +Wk1tRmVxaDJoL1dySDBKWndmOGNtaGsKLT4gWDI1NTE5IHJXWk5mYjFBczVyWnNN +S1B5WWVnaGdhVXRXMGxuY3NrL3VyQXJteUkvUncKSENKVkQramxwU2E1S3dvN0du +cUFzQktMdFVGdjRuaFNaUHBxV3ZaWThvawotPiBzc2gtZWQyNTUxOSBYUG94RFEg +Z2t3TVN6R1p5UDNHNS9LbklKKyszcUI3bHNTZjBOeGQ3ektNeGt6ekpWZwpoZ1Vo +Z0laeStlMDFQRlE1T25Td1pGRFhlWVg5L1JxTG8wU3dwZXpQQlFRCi0+IFRWeScp +KC1ncmVhc2UKbk14RXlNNW5lZXNFNXJoM1ptMHFWTTZmTk5LVnZOcGhRNVIvZjd2 +aTFhRHViWU0KLS0tICt2TnhuME1yUUxqckRacSthQThYOWxkbnl4R2tMc3B4TjRv +WnJMZUhXWmsKwIbI3Wixb/DAac1pHDpRIf+kznq7RKoO/FrSeR6J3gjntMtS8lwW +c+D2NWYqlURR68o5+kJ5dzCpa+oOHy3fnU9yV18fzhOaqz8bWjYpjl1pAxjEIDMO +p5hNsry2WGegLe3dAFwj+c0f52qHCZhcqBvaizUssIN0wkugK6Uq+JtgHMOWMLxg +2qJPc11soq/CfWJvKMzQWMN2ndnjD4s0ZOVLFHuL6/kSFnPlN/1SP3/3Z8cEerm0 +C4GEjDwRei7iHdBuILStgjneJoaxXLZth4ZdsgH/Jd0wmaERg+DytIDqE5ryRG6f +Jo2VR/wUvq+UGgJuCAo6L6vGtBHuwTo7X5azQQwlRCdg +-----END AGE ENCRYPTED FILE----- diff --git a/hosts/by-name/server2/secrets/nextcloud/adminpassFile.age b/hosts/by-name/server2/secrets/nextcloud/adminpassFile.age new file mode 100644 index 0000000..2b831f3 --- /dev/null +++ b/hosts/by-name/server2/secrets/nextcloud/adminpassFile.age @@ -0,0 +1,14 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAxanRqM3pOT29DWitxUERF +RjRURVZUWGpOSzFoZVpCSzJIN0RGZDAzWEZzCkczaExmOGgxQUQwV2NleUdHMXBB +eTZLZXpGZE1hcXBhWWhVcEQ5OFBQWUkKLT4gWDI1NTE5IG5DVmlEaDhTWk9xaWs2 +TnF1K1ZtK2UyeDgvOEFlbVUzc0V1VnZoSmFobEkKczNrSGdwZEVxVFNES3dzcVgy +SmRYNS9WR29mNGNCQW84bHZsZzNTRGZCQQotPiBzc2gtZWQyNTUxOSBYUG94RFEg +Y2ZQd25odWRRbUdqM3gxMzBrQ1Y3UjRwQ0JsRldtblRaYnVKWHZud2p5awpZdkNm +bk82RVVId2tsOXlKb2psa25pNFpManN4bjQwblBWUWdGaWxxQ1B3Ci0+ICNqaTBs +KE8tZ3JlYXNlIE1KZGEgNzAKSGNMSlRGTzN5d3ZXcFZNTkxlZHprVlVQTzJ4K2Vp +MG1YMTFHRmV6L2tMMGUKLS0tIE44WHBBZk1qcmRnK2lPczdiQ294SW50UFdrUHUw +d0EreHNod29LL2pjd2cKw1rpd83gONZaOiV5lQ+QOtIZzoIkaOvRD/8avsbeFsP7 +AB/jiOfOwYJ0DVbNMjopwnzpcAFsLWs6Gg0wQhjNsl349TIcTAS0xLTJYTm8DwQy +FmzftPMHAuJ/IPGzb3hQOFqpuFAPkunfiOgZ/N7N/+LLNMC5NxkkSH8m8gDBCCk= +-----END AGE ENCRYPTED FILE----- diff --git a/hosts/by-name/server3/configuration.nix b/hosts/by-name/server3/configuration.nix new file mode 100644 index 0000000..7f5bce5 --- /dev/null +++ b/hosts/by-name/server3/configuration.nix @@ -0,0 +1,112 @@ +{lib, ...}: { + imports = [ + ./networking.nix # network configuration that just works + ./hardware.nix + ]; + + vhack = { + backup = { + enable = true; + privateSshKey = ./secrets/backup/backupssh.age; + privatePassword = ./secrets/backup/backuppass.age; + user = "u384702-sub4"; + }; + dns = { + enable = true; + openFirewall = true; + interfaces = [ + "92.60.38.179" + "2a03:4000:33:25b::4f4e" + ]; + zones = import ../../../zones {inherit lib;}; + }; + fail2ban.enable = true; + nix-sync = { + enable = true; + domains = import ./websites.nix {}; + }; + mastodon = { + enable = true; + domain = "mastodon.vhack.eu"; + enableTLD = false; + tld = "vhack.eu"; + mailPwFile = ./secrets/mastodon/mail.age; + }; + matrix = { + enable = true; + fqdn = "matrix.vhack.eu"; + url = "vhack.eu"; + sharedSecretFile = ./secrets/matrix/passwd.age; + }; + miniflux = { + enable = true; + domain = "miniflux.foss-syndicate.org"; + extraDomains = [ + "rss.foss-syndicate.org" + "rss.vhack.eu" + "miniflux.vhack.eu" + ]; + adminCredentialsFile = ./secrets/miniflux/admin.age; + }; + murmur = { + enable = true; + host = "mumble.vhack.eu"; + name = "vhack"; + url = "vhack.eu"; + }; + nixconfig.enable = true; + openssh.enable = true; + peertube = { + enable = true; + peertubeGeneral = ./secrets/peertube/general.age; + smtpPasswordFile = ./secrets/peertube/smtp.age; + }; + persist = { + enable = true; + directories = [ + "/var/log" + ]; + }; + stalwart-mail = { + enable = true; + fqdn = "mail.vhack.eu"; + admin = "admin@vhack.eu"; + security = { + dkimKeys = let + loadKey = name: { + dkimPublicKey = builtins.readFile (./secrets/dkim + "/${name}-public"); + dkimPrivateKeyPath = ./secrets/dkim + "/${name}-private.age"; + keyAlgorithm = "ed25519-sha256"; + }; + in { + "mail.vhack.eu" = loadKey "mail.vhack.eu"; + }; + verificationMode = "strict"; + }; + openFirewall = true; + principals = [ + { + class = "individual"; + name = "soispha"; + secret = "$2b$05$XX36sJuHNbTFvi8DFldscOeQBHahluSkiUqD9QGzQaET7NJusSuQW"; + email = [ + "soispha@vhack.eu" + "abuse@vhack.eu" + "postmaster@vhack.eu" + "admin@vhack.eu" + ]; + } + ]; + }; + postgresql.enable = true; + rust-motd.enable = true; + users.enable = true; + }; + + boot.tmp.cleanOnBoot = true; + zramSwap.enable = true; + networking.hostName = "server3"; + networking.domain = "vhack.eu"; + + system.stateVersion = "24.11"; +} diff --git a/hosts/by-name/server1/hardware.nix b/hosts/by-name/server3/hardware.nix index 9abc64c..a6e4e40 100644 --- a/hosts/by-name/server1/hardware.nix +++ b/hosts/by-name/server3/hardware.nix @@ -9,6 +9,7 @@ # FIXME: Find a better way to specify the disk disk = "/dev/vda"; }; + boot.initrd.availableKernelModules = ["ata_piix" "uhci_hcd" "virtio_pci" "sr_mod" "virtio_blk"]; - boot.initrd.kernelModules = []; + nixpkgs.hostPlatform = "x86_64-linux"; } diff --git a/hosts/by-name/server1/networking.nix b/hosts/by-name/server3/networking.nix index dd9b9af..9f4eb27 100644 --- a/hosts/by-name/server1/networking.nix +++ b/hosts/by-name/server3/networking.nix @@ -3,12 +3,11 @@ # details gathered from the active system. networking = { nameservers = [ - "8.8.8.8" + "46.38.225.230" + "46.38.252.230" + "2a03:4000:0:1::e1e6" ]; - defaultGateway = { - address = "89.58.56.1"; - interface = "eth0"; - }; + defaultGateway = "92.60.36.1"; defaultGateway6 = { address = "fe80::1"; interface = "eth0"; @@ -19,19 +18,23 @@ eth0 = { ipv4.addresses = [ { - address = "89.58.58.33"; + address = "92.60.38.179"; prefixLength = 22; } ]; ipv6.addresses = [ { - address = "2a03:4000:6a:3f3::1"; + address = "2a03:4000:33:25b::4f4e"; + prefixLength = 64; + } + { + address = "fe80::98ed:a0ff:fecb:ea48"; prefixLength = 64; } ]; ipv4.routes = [ { - address = "89.58.56.1"; + address = "92.60.36.1"; prefixLength = 32; } ]; @@ -44,9 +47,8 @@ }; }; }; - - # cat /sys/class/net/eth0/address services.udev.extraRules = '' - ATTR{address}=="66:22:6d:82:93:9b", NAME="eth0" + ATTR{address}=="9a:ed:a0:cb:ea:48", NAME="eth0" + ''; } diff --git a/hosts/by-name/server3/secrets/backup/backuppass.age b/hosts/by-name/server3/secrets/backup/backuppass.age new file mode 100644 index 0000000..e7eea19 --- /dev/null +++ b/hosts/by-name/server3/secrets/backup/backuppass.age @@ -0,0 +1,13 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6cUM5S1FKZis0R0o2czRs +cnVCQXlqdXNDWjMvSVlwUEF5S1pTKzNNR2w0ClgycUdEc1EyMjhJZ2lBMjhXVk5n +V3djaVduV3Q4RWw1KzJNQXNYdmhjR1UKLT4gWDI1NTE5IG44TU9lcGc2NkRmczVS +R1hkd0xyVUZwYWVRM05PZzhCK3BkMGFkUDJobXcKejhGMHpQWG4zdnU3WmFBNkhG +Wk5kZy9UWThQcUdRLzBNbEE1c3VrTXdURQotPiBzc2gtZWQyNTUxOSBweXU5Ymcg +RkMwdENYRUFSRHoxTDRHK2xsQndTekJSZ3NmWnlMMW11TjkxTWpMQnJTTQpOSVF5 +RzQ0aXpIeUkyeWJPdlFoWHJPSy9lU2tVUFNOQUVPNXRrZUE4SnN3Ci0+IEEjQUVl +XGxgLWdyZWFzZSA9WVwxaU4hTgpkbTIyMDBuSWhsSEJueGMKLS0tIEh3ZEhoN0FI +NnlUa2ZHdVFmWkVQY3h2ejM4ZkUzcEc1MEcydlRzdVA5UGcKhFaeVepKkQHcbhHS +uxZnlCZoJHEFhc4vCK0w588WJIfkilDk7b5uH/Cn8kWFWLsX0FFe/kk350gEVVm7 +UUndM/+sAEoVzQR8HO1XWGZDd1T70myysBsutA== +-----END AGE ENCRYPTED FILE----- diff --git a/hosts/by-name/server3/secrets/backup/backupssh.age b/hosts/by-name/server3/secrets/backup/backupssh.age new file mode 100644 index 0000000..ae8c5ec --- /dev/null +++ b/hosts/by-name/server3/secrets/backup/backupssh.age @@ -0,0 +1,22 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTNjNnRUMzK1FWWFNEQUtC +ZjF0dFVVTllrYW0ySEt3eHNyL1RhbkZDeWowCnBldm9oSHhuUm1EM3JXbnRZc3JB +WGVNZGdSNm45L3JEenNlcEZqSXdaS0EKLT4gWDI1NTE5IGpZaTA3RUNGbXF4a1Ji +MWJwRkZkM3dqaldMN2d5Wm9mbmxoQmhKeldNeUEKZ0dQZlU1MVhTLzlGMVNSZEhG +MEo2cGxZUXhnbEF2OXFiWjk4bmZIaVdSNAotPiBzc2gtZWQyNTUxOSBweXU5Ymcg +UEg4a05hMGQxUmZPMExzOXZtTVMySWdibHdudDFSWkFuUXlveFFOQnl3Zwp0QkZY +QStEeCtKMXZFd3hmVkd3NXZuK0hKdWxSMzBoMjhuV2thd0dxR0IwCi0+IGtfJFgt +Z3JlYXNlIFJgYHggfTh8QEogJDx+J2tcCjhja3owNWtBVmhSeFIyK0xIcWplMG1m +RiszK05oZktPTVlpSXFRTFVTaWVBeEFCdTZuRWMvdHJFYU10NlNpVGYKYnhkOEor +c1c2ZwotLS0gMmR1djFRTGJ2Qy9hODdGa1RFSVRxQk4rTFB6WW1YZnN2bFhrRDF3 +ZENqNAoTSBXv8NPsyt2RH+qJcbsMMhJ0qqCmyeUWF3Uicv6fiN99TB7xjD6lRXdB +utfLiuBr0gt73QEb44AQFAGzG3Jig9Ql/UFubeKaMRVBscQ4FJXYnHlEK8aB7sVs +k6VgI/Uvs6YH3YDlATfCaD8d/ASG30whH1TcgH6KF3GPX112uUqkIscGifFz4wxu +Fa8Av9XmkBdIQAPS3ze10O866m5Fv4vWeJZ1KEhzV+0nSrBZKPS9a2JqI1c63kz8 +2txZHm26gS4duDqncwnL41jmZ5GX7+TWTj3adIBQrXVSlUPb9h4t5NX2IMS1Fuj8 +UuvKDZplTGEmIJZGoF79VOqOhoCUg9+lqEd53BaAKlLSuHrUeZ1v0IhhquMiOMSt +TrtuhEvdhiH92eWOBNkDNeoEzxU1wCLc1YOk7QCAQEOy0HM5oMntlbMDc+4QmZXz +1QYQKEEMVAi4B53Mm4OFwHTi6GMqDT2r6PsP86uzCB1F8V7q2LDmPnD1rGTQ46al +N8XFq/3uEqd/yNaZU6kffpdK25ibytmvLhjWQ+0LNrUtfftqeTZzaxApQc6bGW5K +KbBnN1A= +-----END AGE ENCRYPTED FILE----- diff --git a/hosts/by-name/server3/secrets/dkim/gen_key.sh b/hosts/by-name/server3/secrets/dkim/gen_key.sh new file mode 100755 index 0000000..61da156 --- /dev/null +++ b/hosts/by-name/server3/secrets/dkim/gen_key.sh @@ -0,0 +1,33 @@ +#! /usr/bin/env nix-shell +#! nix-shell -p rage -p openssl -p bash -i bash --impure + +# shellcheck shell=bash + +cd "$(dirname "$0")" || { + echo "No basedir?!" + exit 1 +} + +key_name="$1" +[ -z "$key_name" ] && { + echo "Usage: $0 KEY_NAME IDENTITY" + exit 2 +} + +openssl genpkey \ + -algorithm ed25519 \ + -out - | + tee >(openssl pkey \ + -pubout \ + -out - | + openssl asn1parse \ + -offset 12 \ + -noout \ + -out - | + base64 --wrap 0 >"$key_name-public") | + rage --encrypt \ + --armor \ + --recipient "age1mshh4ynzhhzhff25tqwkg4j054g3xwrfznh98ycchludj9wjj48qn2uffn" \ + >"$key_name-private.age" + +# vim: ft=sh diff --git a/hosts/by-name/server3/secrets/dkim/mail.vhack.eu-private.age b/hosts/by-name/server3/secrets/dkim/mail.vhack.eu-private.age new file mode 100644 index 0000000..8d66808 --- /dev/null +++ b/hosts/by-name/server3/secrets/dkim/mail.vhack.eu-private.age @@ -0,0 +1,16 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqdEtVSWhiOVR1N1Q5bTBV +NXRMMm42VlR5NitSWlhiSFpUZDZQSlloWlJ3ClA3ZEJSU2dDbmRVL0NMZlFOVU5J +V1lEbDM0MlN3S3dZMUkyc1pQZVVpdDAKLT4gWDI1NTE5IFk4YnFFZmFLTlA0WENY +K3FGME1CbUV4b0Z4V1FIRFBmNVphYmhCMG1QVkkKOGhFcnl3Y2hZQU8rY0ROMTlq +d0lUVG8rRWpPNm4vWkw2WFROU3NJalgzWQotPiBzc2gtZWQyNTUxOSBweXU5Ymcg +UDV4YUdZRWZieHN4RVU1WWEvdlFRVHpTL1V5Q3Nya1kvNjFxVytpS1NGawpUWnlR +RmtQL1Z1ZFkwTC9ua3VVb05VQVlLemtuOCtLSkdxbFE5U2wyM0xZCi0+IFktZ3Jl +YXNlCk56M0t2NXB3QVpjYTNFdkEvMmpDZXBPcWlLNXNWL2tPalNMM1g0KzBJL2xz +T1gvTldRLzNxM25BOUhFZml3dFQKSnNMeHBXK3BrS2pWVU1uTkNKZ3BnaGt2Ci0t +LSBuWURsUEYxRkx3bVQzU3JTcGlwUTFCZ09IRWIrNExUclhPSmdGdUtNOFlFCuKw +PBh8U5VmweDGoY+xFXw/nqTqrKw9gZyUR2vbnHdnN9y8BToht7prsEaAn//DVivI +GMFGMhbPYTumWnEiTho8ZqQv5tKiDdIGV/9YghzUdHtMnzfO7q5ztrFYx19qjgi/ +lW17WyY8Jk2DZIH3icYweTICx9IU5K11DNj6WgNGDe8/fAyfuHTekE8sZtHPDw76 +M3wkUZM= +-----END AGE ENCRYPTED FILE----- diff --git a/hosts/by-name/server3/secrets/dkim/mail.vhack.eu-public b/hosts/by-name/server3/secrets/dkim/mail.vhack.eu-public new file mode 100644 index 0000000..fa5d243 --- /dev/null +++ b/hosts/by-name/server3/secrets/dkim/mail.vhack.eu-public @@ -0,0 +1 @@ +U0eOxgLD3yK7PKzQRSZdJ3EH/UwVxPeYmfm42gYXsDg= diff --git a/hosts/by-name/server3/secrets/mastodon/mail.age b/hosts/by-name/server3/secrets/mastodon/mail.age new file mode 100644 index 0000000..882ade9 --- /dev/null +++ b/hosts/by-name/server3/secrets/mastodon/mail.age @@ -0,0 +1,14 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPeTFoTjB6RTZHbDVzUzg2 +SzNnSE9aSi9yZUNSWWVKNlQxWUo1Y2M1R1h3CjR0RW8xdEtUTlBTcU9DWWE2OVVX +WEJVVkF2bmtQaUxrK0Vpb21qSCtUcncKLT4gWDI1NTE5IG1JY25Jdmo3UWt4aXJK +VTRFZVNja2R6MzlJcVMvdHhqZTY0WS91Vnp3Vk0KUG4xbVR2V3k0OFJCVFplODcw +R0ZDSExRTzVpRWVyM0E4VVRvMXE5cHpWUQotPiBzc2gtZWQyNTUxOSBweXU5Ymcg +RFFHaXFrS0IyWnVYdDE5aFhHNnZFSFY3S1ZVZHovRTRrV3VKV3JBQnJVTQordzJ5 +V0hpZ3dsdDVHODluNnRzWlBHRFBBcnVya0dMNTU3T2Z3NkpVZHBvCi0+IFB7LX5l +Vm5wLWdyZWFzZSA8NVIgV08zU3lBIGBZJSAnRQpwbDZTUTNqdVd4MHFNNVRVZ1pQ +MG1qcUtjVGRreU9zMwotLS0gMVJ4eldEQlRTTmdraDJDM2pzbkZOY0t6Wnl6TDd1 +cFRXZXJmS1FTMEtyNApWNUWWIXokgwgI+2GT+sBkTzFbXM4CPpIq2QOGRWMrRMmw +dHoK5NJEI7uw9mP9t6PI04THBqVL5YotJtZkAk1Sx00SWvyLPpZRsSBdH11YiRAb +jIx0T573hbbFoMNlZHoJ +-----END AGE ENCRYPTED FILE----- diff --git a/hosts/by-name/server3/secrets/matrix/passwd.age b/hosts/by-name/server3/secrets/matrix/passwd.age new file mode 100644 index 0000000..6386ed6 --- /dev/null +++ b/hosts/by-name/server3/secrets/matrix/passwd.age @@ -0,0 +1,15 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrRFcxajBUb2s4dDVKeVZF +bFE1NUNwS2p0NjhZd2Y0MWNNbFFDcE1VSTJ3Cmdsdmh1MFJ2bWcxVWZlVm1idGdC +aXU3bnlmVkpydXpMYnh2djNURjd6L0UKLT4gWDI1NTE5IHRidGtkVGZDV0Npck9q +Y1pRYjVUVWVYMkZxcCtyTGRkQWRGQXB1dEhVR3cKQzNwQndqZTBHTVBnbUg5bWNk +ZFpOSG1UZzZXQ2kxQjRXUS80Tmx0ZURiMAotPiBzc2gtZWQyNTUxOSBweXU5Ymcg +YmNaeGV2WTJqZFFSTXhDS1hScDZrV1ZWU1FyYWRtSGNoR3NGUjZ0WmpqSQptRnR5 +cDI4VDFXL2t3VzdnSGF5VzBIbzhzU1NuQmNuUXhReHNVNGd4bnFJCi0+ICJ9OUlg +LWdyZWFzZQpDYks4Y2dUeEowTHh6cnJsNmpXRGpDYWU1RkRwbC9nYjB2RmtMZjhy +dTBhVEU1ak04U0VYUkh0WUJsK3h5cXBRCmZ4ekRRczFDZWptWkJQbXZ6NDU0dUh3 +RTlkVkxxQ00xeHNmMkZSS0JIZGpmOU5UYSt1bWdRNlZWbC9ZdQotLS0gbG9RR0Iv +OTBleHBTS1ZVYjZSODEranR5cGxsTkh1elZwQi9Gd21VbUxkRQoJ+dUdl1CVle6A +sLVikThgDKKpMekZeLhx97gC6Vxfxd9oJiw1SS7xOjMZz6xcOCG1l1NidrNHmhnK +4xQMcvHU+5Ogw3YUnPcL1sGjYWkvgUcwie+WEKZFXkCaJwz91ria +-----END AGE ENCRYPTED FILE----- diff --git a/hosts/by-name/server3/secrets/miniflux/admin.age b/hosts/by-name/server3/secrets/miniflux/admin.age new file mode 100644 index 0000000..12944a5 --- /dev/null +++ b/hosts/by-name/server3/secrets/miniflux/admin.age @@ -0,0 +1,14 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2NlhidUNjVkM2UjRxUTFU +K0gzT1BoR0lTUWRpelo2cEU4UnI2YnZmOGhFCmJwTmh5bmVZVVoxSThucnNWY2dX +NnBBTmFUcXR2TE1VT1ROaUFjeFpjRUkKLT4gWDI1NTE5IDZGUnRNYjFRSERwZlM5 +YnRETzY5MkVFaHpVdnFwTlpQQWxFVlc1dnVVV1kKMlFmVUZkYWhFNGpkMGp0NHQv +Mnd3YTlhaFRGVGp3OXVSNDZCNys1cTZuawotPiBzc2gtZWQyNTUxOSBweXU5Ymcg +ZTBYTGFncjZuRzVSZDFjL2IweCtQeExMOEEvcFhrREFTbTlnZm5kMnZpOApmcnRX +MXVOMHpya1hlaHNMOUI0bmlTVXRwTVFhbGJid3FuUTJkK2NsdkZjCi0+IGx5QDor +OCstZ3JlYXNlCkJ0TkJneDdrMTBxWXVYdk9zRFJCCi0tLSA0TEQvQWpTZldXQjZx +NmlaZnJGL3hCbjM4UzVHTTlrTWJPTm5xRE9aMFMwClDs64cTlulCxY4R+9YtpxSA +0WGrPCpKyS0JVhuO+WgXLm34k+xjSWSER0Uiqu+fotyiX3KSMyjZDAyWMsiDiAlq +CaOHTlpbDZuIIqfmrHsqH5dM4MPHvwigL2zBrXcbarYxVagJk89k31ah+5YJhMsa +kOmNpZlQK/CmrhqIKNFzVYZp+q5Sr6ZTJVo= +-----END AGE ENCRYPTED FILE----- diff --git a/hosts/by-name/server3/secrets/peertube/general.age b/hosts/by-name/server3/secrets/peertube/general.age new file mode 100644 index 0000000..f3cba8b --- /dev/null +++ b/hosts/by-name/server3/secrets/peertube/general.age @@ -0,0 +1,16 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBTYkJyN1JhNnZQRlNDK3hy +aHFwSnVBSWFLK0lhU0hUSE4wLysrcmxlTkhJClpHa2hzSTZobmh5eHNuR0FST2pv +eGJtVmZSSjBGTUVCZ05PdStZRGZjTU0KLT4gWDI1NTE5IGg3ZmZXMTdlTEFBVmt2 +OVExVld0Qkc2bFMyV1NZZTd4bUtkOWdNY0N1M1EKU1UxR2V1emtyeDdkQ0RwUGVv +cVJLcWllK2x4K3JNOExGSktIOW5SbldCbwotPiBzc2gtZWQyNTUxOSBweXU5Ymcg +MGZMUHNCWUFESVVERzN3Q0krUGZ3Z1QwTUdUc0NCRkg4UFdKa2l1ZGJSRQo5YXlB +aENyTW1idUxvVW5vRHp3L2dPVUtZS0dRa0JpaWo5RjdTcEsrK2VRCi0+ICN2bC1n +cmVhc2UgPF0qV3N2IEZ0XTEnRSBxMDc7SWBCCnNJMGNWQkxZOFU0SzJSUUtoK3FI +Ulp0T3FGeDdOUVZVRXRXRWRzSEdkWmFGeEF3RStWU1RMS3BLTlpxU0N1UmkKV3lJ +a05yWm1GMC90VTFFcWpPTlliOUZoUGtMVlJsbTRSbGRyVi9kZDhxM0x4Qk82RUM4 +enZVT1RLNmd5SkEKLS0tIGRucFFNTFNSWnNtamlZTE9hM0k4QzRhZ21FZ2t5Ynpo +Rno5UjVzRUFhcmMKWa8uscZL8FWMZ5zPstM7LraYV4PyuVhOHq3f3BBRr5rkptmK +DHAye+FmVX7+Fqqk0ynyK92v2ti86i/iuWiNzImLWI6xkBruFEo3lpnnc8rAdslR +c+8e2ntLGIRHbTVMwg== +-----END AGE ENCRYPTED FILE----- diff --git a/hosts/by-name/server3/secrets/peertube/smtp.age b/hosts/by-name/server3/secrets/peertube/smtp.age new file mode 100644 index 0000000..e0af2d1 --- /dev/null +++ b/hosts/by-name/server3/secrets/peertube/smtp.age @@ -0,0 +1,15 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBncFdORy9uSXJabkJFVVc5 +QXJnS3FZQzJmTXdsckNxNjU3SGRTUEdnZ3hRCm5DNDVJbEw5VVlTS2k2Y2p6aEhX +QzdKbXVTV3dzSHBYN0RkN1NnL0RTR0kKLT4gWDI1NTE5IEdudmllbmRud1ZVTzI3 +TWQ0cWhKSTkrbkJyelZUUThwNkdoVFhRZW12MFEKUXczc0w3ZjRUTE83UGY5d2gx +YmpHUlJacXAyY2hXSy9aWkxOZmJwaHRqOAotPiBzc2gtZWQyNTUxOSBweXU5Ymcg +Z3ZNSUY2NFA1L0lad3FJWDlvLzVJZDlrdS9Nc3RxZGZHWEk1SlBIeGhRMAo2WnVL +WXcxYWd1aGN0ZVAxb3ZEOXFKMTRFNjcwVFhmRVN0TXJrYXFsV0VzCi0+ID90SSE+ +LWdyZWFzZSA5XjlPCjlENzA2TmkreUZpYllXZ290RHMKLS0tIE0zMHAvWDVWYWdG +S1pGVFdMVWp1R0QzSzVpczhrSGJUNVdKTlpHT0JZRGcKlqZQsbkUV/cp+xQvzQKA +AtBpJl9Fho5Szb+GOL2xEKH6KV6LTI8xaOE2KWRyhNSwH682InD5ilCaCYbHz5aW +u7SfKWTBGj6gjwUlIJEvBzJWIXgXPcoMqgIZNe1HH52IQWJfZN5H01dHjic7mYrm +nW5S5EEczDR6nHTKf7dsZLmbTctb90lM80rlDS5Q16QrR4VPElTJGySu/hLtl1ep +r0w= +-----END AGE ENCRYPTED FILE----- diff --git a/hosts/by-name/server3/websites.nix b/hosts/by-name/server3/websites.nix new file mode 100644 index 0000000..466f1e9 --- /dev/null +++ b/hosts/by-name/server3/websites.nix @@ -0,0 +1,36 @@ +{...}: let + mkWkd = domain: { + domain = "openpgpkey.${domain}"; + repositoryUrl = "https://git.foss-syndicate.org/vhack.eu/pgp-wkd.git"; + extraSettings = { + locations."/.well-known/openpgpkey/".extraConfig = '' + default_type application/octet-stream; + + # Came from: https://www.uriports.com/blog/setting-up-openpgp-web-key-directory/ + # No idea if it is actually necessary + # add_header Access-Control-Allow-Origin * always; + ''; + }; + }; +in [ + { + domain = "vhack.eu"; + repositoryUrl = "https://codeberg.org/vhack.eu/website.git"; + } + { + domain = "b-peetz.de"; + repositoryUrl = "https://git.foss-syndicate.org/bpeetz/b-peetz.de.git"; + } + + # Trinitrix + { + domain = "trinitrix.vhack.eu"; + repositoryUrl = "https://codeberg.org/trinitrix/website.git"; + } + + # WKD + (mkWkd "b-peetz.de") + (mkWkd "s-schoeffel.de") + (mkWkd "sils.li") + (mkWkd "vhack.eu") +] diff --git a/hosts/host-names.toml b/hosts/host-names.toml index fd5b960..2cf2833 100644 --- a/hosts/host-names.toml +++ b/hosts/host-names.toml @@ -1,2 +1,2 @@ -server1 = "server1.vhack.eu" server2 = "server2.vhack.eu" +server3 = "server3.vhack.eu" diff --git a/modules/by-name/ba/back/module.nix b/modules/by-name/ba/back/module.nix index 520acdb..d47ffce 100644 --- a/modules/by-name/ba/back/module.nix +++ b/modules/by-name/ba/back/module.nix @@ -6,116 +6,87 @@ ... }: let cfg = config.vhack.back; +in { + options.vhack.back = { + enable = lib.mkEnableOption "Back issue tracker (inspired by tvix's panettone)"; - mkConfigFile = repoPath: domain: - (pkgs.formats.json {}).generate "config.json" - { - inherit (cfg) source_code_repository_url; - repository_path = repoPath; - root_url = "https://${domain}"; - }; - - mkUnit = repoPath: port: domain: { - description = "Back service for ${repoPath}"; - wants = ["network-online.target"]; - after = ["network-online.target"]; - wantedBy = ["default.target"]; - - environment = { - ROCKET_PORT = builtins.toString port; + domain = lib.mkOption { + type = lib.types.str; + description = "The domain to host this `back` instance on."; }; - serviceConfig = { - ExecStart = "${lib.getExe vhackPackages.back} ${mkConfigFile repoPath domain}"; + settings = { + scan_path = lib.mkOption { + type = lib.types.path; + description = "The path to the directory under which all the repositories reside"; + }; + project_list = lib.mkOption { + type = lib.types.path; + description = "The path to the `projects.list` file."; + }; - # Ensure that the service can read the repository - # FIXME(@bpeetz): This has the implied assumption, that all the exposed git - # repositories are readable for the git group. This should not be necessary. <2024-12-23> - User = "git"; - Group = "git"; + source_code_repository_url = lib.mkOption { + description = "The url to the source code of this instance of back"; + default = "https://git.foss-syndicate.org/vhack.eu/nixos-server/tree/pkgs/by-name/ba/back"; + type = lib.types.str; + }; - DynamicUser = true; - Restart = "always"; - - # Sandboxing - ProtectSystem = "strict"; - ProtectHome = true; - PrivateTmp = true; - PrivateDevices = true; - ProtectHostname = true; - ProtectClock = true; - ProtectKernelTunables = true; - ProtectKernelModules = true; - ProtectKernelLogs = true; - ProtectControlGroups = true; - RestrictAddressFamilies = ["AF_UNIX" "AF_INET" "AF_INET6"]; - RestrictNamespaces = true; - LockPersonality = true; - MemoryDenyWriteExecute = true; - RestrictRealtime = true; - RestrictSUIDSGID = true; - RemoveIPC = true; - PrivateMounts = true; - # System Call Filtering - SystemCallArchitectures = "native"; - SystemCallFilter = ["~@cpu-emulation @debug @keyring @mount @obsolete @privileged @setuid"]; + root_url = lib.mkOption { + type = lib.types.str; + description = "The url to this instance of back."; + default = "https://${cfg.domain}"; + }; }; }; - mkVirtalHost = port: { - locations."/".proxyPass = "http://127.0.0.1:${builtins.toString port}"; + config = lib.mkIf cfg.enable { + systemd.services."back" = { + description = "Back issue tracking system."; + requires = ["network-online.target"]; + after = ["network-online.target"]; + wantedBy = ["default.target"]; - enableACME = true; - forceSSL = true; - }; + serviceConfig = { + ExecStart = "${lib.getExe vhackPackages.back} ${(pkgs.formats.json {}).generate "config.json" cfg.settings}"; - services = - lib.mapAttrs' (gitPath: config: { - name = builtins.replaceStrings ["/"] ["_"] "back-${config.domain}"; - value = mkUnit gitPath config.port config.domain; - }) - cfg.repositories; + # Ensure that the service can read the repository + # FIXME(@bpeetz): This has the implied assumption, that all the exposed git + # repositories are readable for the git group. This should not be necessary. <2024-12-23> + User = "git"; + Group = "git"; - virtualHosts = - lib.mapAttrs' (gitPath: config: { - name = config.domain; - value = mkVirtalHost config.port; - }) - cfg.repositories; -in { - options.vhack.back = { - enable = lib.mkEnableOption "Back issue tracker (inspired by tvix's panettone)"; + DynamicUser = true; + Restart = "always"; - source_code_repository_url = lib.mkOption { - description = "The url to the source code of this instance of back"; - default = "https://git.foss-syndicate.org/vhack.eu/nixos-server/tree/pkgs/by-name/ba/back"; - type = lib.types.str; + # Sandboxing + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + PrivateDevices = true; + ProtectHostname = true; + ProtectClock = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectKernelLogs = true; + ProtectControlGroups = true; + RestrictAddressFamilies = ["AF_UNIX" "AF_INET" "AF_INET6"]; + RestrictNamespaces = true; + LockPersonality = true; + MemoryDenyWriteExecute = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + RemoveIPC = true; + PrivateMounts = true; + # System Call Filtering + SystemCallArchitectures = "native"; + SystemCallFilter = ["~@cpu-emulation @debug @keyring @mount @obsolete @privileged @setuid"]; + }; }; + services.nginx.virtualHosts."${cfg.domain}" = { + locations."/".proxyPass = "http://127.0.0.1:8000"; - repositories = lib.mkOption { - description = "An attibute set of repos to launch `back` services for."; - type = lib.types.attrsOf (lib.types.submodule { - options = { - enable = (lib.mkEnableOption "`back` for this repository.") // {default = true;}; - domain = lib.mkOption { - type = lib.types.str; - description = "The domain to host this `back` instance on."; - }; - port = lib.mkOption { - type = lib.types.port; - - # TODO: This _should_ be an implementation detail, but I've no real approach to - # automatically generate them without encountering weird bugs. <2024-12-23> - description = "The port to use for this back instance. This must be unique."; - }; - }; - }); - default = {}; + enableACME = true; + forceSSL = true; }; }; - - config = lib.mkIf cfg.enable { - systemd = {inherit services;}; - services.nginx = {inherit virtualHosts;}; - }; } diff --git a/modules/by-name/ba/backup/module.nix b/modules/by-name/ba/backup/module.nix new file mode 100644 index 0000000..856a1c3 --- /dev/null +++ b/modules/by-name/ba/backup/module.nix @@ -0,0 +1,91 @@ +{ + config, + pkgs, + lib, + ... +}: let + cfg = config.vhack.backup; + snapshots = "/srv/snapshots"; + postgresUser = "postgres"; +in { + options.vhack.backup = { + enable = lib.mkEnableOption "backups with restic"; + user = lib.mkOption { + type = lib.types.str; + description = "The storagebox-user to use"; + example = "u384702-sub2"; + }; + privateSshKey = lib.mkOption { + type = lib.types.path; + description = "The age-encrypted ssh-key, passed to agenix"; + }; + privatePassword = lib.mkOption { + type = lib.types.path; + description = "The age-encrypted restic password, passed to agenix"; + }; + }; + config = lib.mkIf cfg.enable { + vhack.persist.directories = [ + { + directory = "/root/.ssh"; + user = "root"; + group = "root"; + mode = "0700"; + } + ]; + age.secrets = { + resticpass = { + file = cfg.privatePassword; + mode = "0700"; + owner = "root"; + group = "root"; + }; + resticssh = { + file = cfg.privateSshKey; + mode = "0700"; + owner = "root"; + group = "root"; + }; + }; + services.restic.backups = { + storagebox = { + initialize = true; + backupPrepareCommand = '' + ${pkgs.sudo}/bin/sudo -u ${postgresUser} ${pkgs.postgresql}/bin/pg_dumpall --clean --if-exists --quote-all-identifiers > /srv/db_backup.sql + + [ -d /srv/snapshots ] || ${pkgs.btrfs-progs}/bin/btrfs subvolume create /srv/snapshots; + [ -d /srv/snapshots/srv ] && ${pkgs.btrfs-progs}/bin/btrfs subvolume delete /srv/snapshots/srv; + ${pkgs.btrfs-progs}/bin/btrfs subvolume snapshot -r /srv /srv/snapshots/srv; + + # dump() { + # # compression: + # # pg_dump -F t -v "$1" | xz -z -9 -e -T0 > "db_$1.tar.xz" + # pg_dump -v "$1" > "db_$1.tar.xz" + # } + # # List all databases, and dump each of them in its own file + # # psql --list --csv | while read -r line; do echo "$line" | grep ','; done | while IFS=, read -r name _; do echo "$name"; done | sed '1d' | while read -r db_name; do dump "$db_name"; done + ''; + paths = [ + snapshots + ]; + exclude = [ + ".snapshots" + "/var/lib/postgresql" # included in the db dump + ]; + extraBackupArgs = [ + "--verbose" # spam log + ]; + passwordFile = config.age.secrets.resticpass.path; + extraOptions = [ + "rclone.program='ssh -p 23 ${cfg.user}@${cfg.user}.your-storagebox.de -i ${config.age.secrets.resticssh.path}'" + ]; + repository = "rclone: "; # There is only one repository served + timerConfig = { + Requires = "network-online.target"; + OnCalendar = "daily"; + Persistent = true; + }; + }; + }; + }; +} diff --git a/modules/by-name/co/constants/module.nix b/modules/by-name/co/constants/module.nix index a28ea0c..7eaa8b4 100644 --- a/modules/by-name/co/constants/module.nix +++ b/modules/by-name/co/constants/module.nix @@ -1,41 +1,85 @@ # This file is inspired by the `nixos/modules/misc/ids.nix` # file in nixpkgs. -{lib, ...}: { +{ + lib, + config, + ... +}: { options.vhack.constants = { ids.uids = lib.mkOption { internal = true; description = '' The user IDs used in the vhack.eu nixos config. ''; - type = lib.types.attrsOf lib.types.int; + type = lib.types.attrsOf (lib.types.ints.between 0 400); }; ids.gids = lib.mkOption { internal = true; description = '' The group IDs used in the vhack.eu nixos config. ''; - type = lib.types.attrsOf lib.types.int; + type = lib.types.attrsOf (lib.types.ints.between 0 400); }; }; config.vhack.constants = { ids.uids = { + # Keep this sorted with `!sort --numeric-sort --key=2 --field-separator="="` + opendkim = 221; + mautrix-whatsapp = 222; + etebase-server = 223; + matrix-synapse = 224; + rspamd = 225; + knot-resolver = 226; + peertube = 231; + redis-mastodon = 232; + redis-peertube = 233; + redis-rspamd = 234; + redis-stalwart-mail = 235; + mastodon = 236; + stalwart-mail = 238; acme = 328; dhcpcd = 329; nscd = 330; sshd = 331; systemd-oom = 332; + nix-sync = 334; + nextcloud = 335; + redis-nextcloud = 336; # As per the NixOS file, the uids should not be greater or equal to 400; }; - ids.gids = { - acme = 328; - dhcpcd = 329; - nscd = 330; - sshd = 331; - systemd-oom = 332; - resolvconf = 333; # This group is not matched to an user? + ids.gids = let + inherit (config.vhack.constants.ids) uids; + in { + inherit + (uids) + acme + dhcpcd + etebase-server + knot-resolver + mastodon + matrix-synapse + mautrix-whatsapp + nextcloud + nix-sync + nscd + opendkim + peertube + redis-mastodon + redis-nextcloud + redis-peertube + redis-rspamd + redis-stalwart-mail + rspamd + sshd + stalwart-mail + systemd-oom + ; + + # Keep this sorted with `!sort --numeric-sort --key=2 --field-separator="="` systemd-coredump = 151; # matches systemd-coredump user + resolvconf = 333; # This group is not matched to an user? # The gid should match the uid. Thus should not be >= 400; }; diff --git a/modules/by-name/dn/dns/dns/default.nix b/modules/by-name/dn/dns/dns/default.nix new file mode 100644 index 0000000..4ce07d8 --- /dev/null +++ b/modules/by-name/dn/dns/dns/default.nix @@ -0,0 +1,13 @@ +# +# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/> +# +# SPDX-License-Identifier: MPL-2.0 or MIT +# +{lib}: let + util = import ./util {inherit lib;}; + types = import ./types {inherit lib util;}; +in { + inherit + types + ; +} diff --git a/modules/by-name/dn/dns/dns/types/default.nix b/modules/by-name/dn/dns/dns/types/default.nix new file mode 100644 index 0000000..ece315f --- /dev/null +++ b/modules/by-name/dn/dns/dns/types/default.nix @@ -0,0 +1,16 @@ +# +# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/> +# +# SPDX-License-Identifier: MPL-2.0 or MIT +# +{ + lib, + util, +}: let + simple = {types = import ./simple.nix {inherit lib;};}; +in { + record = import ./record.nix {inherit lib util;}; + records = import ./records {inherit lib util simple;}; + + zone = import ./zone.nix {inherit lib util simple;}; +} diff --git a/modules/by-name/dn/dns/dns/types/record.nix b/modules/by-name/dn/dns/dns/types/record.nix new file mode 100644 index 0000000..e992bf9 --- /dev/null +++ b/modules/by-name/dn/dns/dns/types/record.nix @@ -0,0 +1,75 @@ +# +# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/> +# SPDX-FileCopyrightText: 2021 Naïm Favier <n@monade.li> +# +# SPDX-License-Identifier: MPL-2.0 or MIT +# +{lib, ...}: let + inherit (lib) hasSuffix isString mkOption removeSuffix types; + + recordType = rsubt: let + submodule = types.submodule { + options = + { + class = mkOption { + type = types.enum ["IN"]; + default = "IN"; + example = "IN"; + description = "Resource record class. Only IN is supported"; + }; + ttl = mkOption { + type = types.nullOr types.ints.unsigned; # TODO: u32 + default = null; + example = 300; + description = "Record caching duration (in seconds)"; + }; + } + // rsubt.options; + }; + in + ( + if rsubt ? fromString + then types.either types.str + else lib.id + ) + submodule; + + # name == "@" : use unqualified domain name + writeRecord = name: rsubt: data: let + data' = + if isString data && rsubt ? fromString + then + # add default values for the record type + (recordType rsubt).merge [] [ + { + file = ""; + value = rsubt.fromString data; + } + ] + else data; + name' = let + fname = rsubt.nameFixup or (n: _: n) name data'; + in + if name == "@" + then name + else if (hasSuffix ".@" name) + then removeSuffix ".@" fname + else "${fname}."; + inherit (rsubt) rtype; + in + lib.concatStringsSep " " (with data'; + [ + name' + ] + ++ lib.optionals (ttl != null) [ + (toString ttl) + ] + ++ [ + class + rtype + (rsubt.dataToString data') + ]); +in { + inherit recordType; + inherit writeRecord; +} diff --git a/modules/by-name/dn/dns/dns/types/records/A.nix b/modules/by-name/dn/dns/dns/types/records/A.nix new file mode 100644 index 0000000..296943e --- /dev/null +++ b/modules/by-name/dn/dns/dns/types/records/A.nix @@ -0,0 +1,19 @@ +# +# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/> +# +# SPDX-License-Identifier: MPL-2.0 or MIT +# +{lib, ...}: let + inherit (lib) mkOption types; +in { + rtype = "A"; + options = { + address = mkOption { + type = types.str; + example = "26.3.0.103"; + description = "IP address of the host"; + }; + }; + dataToString = {address, ...}: address; + fromString = address: {inherit address;}; +} diff --git a/modules/by-name/dn/dns/dns/types/records/AAAA.nix b/modules/by-name/dn/dns/dns/types/records/AAAA.nix new file mode 100644 index 0000000..4717176 --- /dev/null +++ b/modules/by-name/dn/dns/dns/types/records/AAAA.nix @@ -0,0 +1,19 @@ +# +# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/> +# +# SPDX-License-Identifier: MPL-2.0 or MIT +# +{lib, ...}: let + inherit (lib) mkOption types; +in { + rtype = "AAAA"; + options = { + address = mkOption { + type = types.str; + example = "4321:0:1:2:3:4:567:89ab"; + description = "IPv6 address of the host"; + }; + }; + dataToString = {address, ...}: address; + fromString = address: {inherit address;}; +} diff --git a/modules/by-name/dn/dns/dns/types/records/CAA.nix b/modules/by-name/dn/dns/dns/types/records/CAA.nix new file mode 100644 index 0000000..4b40510 --- /dev/null +++ b/modules/by-name/dn/dns/dns/types/records/CAA.nix @@ -0,0 +1,42 @@ +# +# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/> +# +# SPDX-License-Identifier: MPL-2.0 or MIT +# +# RFC 8659 +{lib, ...}: let + inherit (lib) mkOption types; +in { + rtype = "CAA"; + options = { + issuerCritical = mkOption { + type = types.bool; + example = true; + description = '' + If set to '1', indicates that the corresponding property tag + MUST be understood if the semantics of the CAA record are to be + correctly interpreted by an issuer + ''; + }; + tag = mkOption { + type = types.enum ["issue" "issuewild" "iodef"]; + example = "issue"; + description = "One of the defined property tags"; + }; + value = mkOption { + type = types.str; # section 4.1.1: not limited in length + example = "ca.example.net"; + description = "Value of the property"; + }; + }; + dataToString = { + issuerCritical, + tag, + value, + ... + }: ''${ + if issuerCritical + then "128" + else "0" + } ${tag} "${value}"''; +} diff --git a/modules/by-name/dn/dns/dns/types/records/CNAME.nix b/modules/by-name/dn/dns/dns/types/records/CNAME.nix new file mode 100644 index 0000000..095b078 --- /dev/null +++ b/modules/by-name/dn/dns/dns/types/records/CNAME.nix @@ -0,0 +1,27 @@ +# +# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/> +# +# SPDX-License-Identifier: MPL-2.0 or MIT +# +# RFC 1035, 3.3.1 +{ + lib, + simple, + ... +}: let + inherit (lib) mkOption; +in { + rtype = "CNAME"; + options = { + cname = mkOption { + type = simple.types.domain-name; + example = "www.test.com"; + description = '' + A <domain-name> which specifies the canonical or primary name + for the owner. The owner name is an alias. + ''; + }; + }; + dataToString = {cname, ...}: "${cname}"; + fromString = cname: {inherit cname;}; +} diff --git a/modules/by-name/dn/dns/dns/types/records/DKIM.nix b/modules/by-name/dn/dns/dns/types/records/DKIM.nix new file mode 100644 index 0000000..31b2f67 --- /dev/null +++ b/modules/by-name/dn/dns/dns/types/records/DKIM.nix @@ -0,0 +1,75 @@ +# +# SPDX-FileCopyrightText: 2020 Kirill Elagin <https://kir.elagin.me/> +# +# SPDX-License-Identifier: MPL-2.0 or MIT +# +# This is a “fake” record type, not actually part of DNS. +# It gets compiled down to a TXT record. +# RFC 6376 +{ + lib, + util, + ... +}: let + inherit (lib) mkOption types; +in rec { + rtype = "TXT"; + options = { + selector = mkOption { + type = types.str; + example = "mail"; + description = "DKIM selector name"; + }; + h = mkOption { + type = types.listOf types.str; + default = []; + example = ["sha1" "sha256"]; + description = "Acceptable hash algorithms. Empty means all of them"; + apply = lib.concatStringsSep ":"; + }; + k = mkOption { + type = types.nullOr types.str; + default = "rsa"; + example = "rsa"; + description = "Key type"; + }; + n = mkOption { + type = types.str; + default = ""; + example = "Just any kind of arbitrary notes."; + description = "Notes that might be of interest to a human"; + }; + p = mkOption { + type = types.str; + example = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDwIRP/UC3SBsEmGqZ9ZJW3/DkMoGeLnQg1fWn7/zYtIxN2SnFCjxOCKG9v3b4jYfcTNh5ijSsq631uBItLa7od+v/RtdC2UzJ1lWT947qR+Rcac2gbto/NMqJ0fzfVjH4OuKhitdY9tf6mcwGjaNBcWToIMmPSPDdQPNUYckcQ2QIDAQAB"; + description = "Public-key data (base64)"; + }; + s = mkOption { + type = types.listOf (types.enum ["*" "email"]); + default = ["*"]; + example = ["email"]; + description = "Service Type"; + apply = lib.concatStringsSep ":"; + }; + t = mkOption { + type = types.listOf (types.enum ["y" "s"]); + default = []; + example = ["y"]; + description = "Flags"; + apply = lib.concatStringsSep ":"; + }; + }; + dataToString = data: let + items = + ["v=DKIM1"] + ++ lib.pipe data [ + (builtins.intersectAttrs options) # remove garbage list `_module` + (lib.filterAttrs (_k: v: v != null && v != "")) + (lib.filterAttrs (k: _v: k != "selector")) + (lib.mapAttrsToList (k: v: "${k}=${v}")) + ]; + result = lib.concatStringsSep "; " items + ";"; + in + util.writeCharacterString result; + nameFixup = name: self: "${self.selector}._domainkey.${name}"; +} diff --git a/modules/by-name/dn/dns/dns/types/records/DMARC.nix b/modules/by-name/dn/dns/dns/types/records/DMARC.nix new file mode 100644 index 0000000..0f10f2c --- /dev/null +++ b/modules/by-name/dn/dns/dns/types/records/DMARC.nix @@ -0,0 +1,108 @@ +# +# SPDX-FileCopyrightText: 2020 Kirill Elagin <https://kir.elagin.me/> +# +# SPDX-License-Identifier: MPL-2.0 or MIT +# +# This is a “fake” record type, not actually part of DNS. +# It gets compiled down to a TXT record. +# RFC 7489 +{ + lib, + util, + ... +}: let + inherit (lib) mkOption types; +in rec { + rtype = "TXT"; + options = { + adkim = mkOption { + type = types.enum ["relaxed" "strict"]; + default = "relaxed"; + example = "strict"; + description = "DKIM Identifier Alignment mode"; + apply = builtins.substring 0 1; + }; + aspf = mkOption { + type = types.enum ["relaxed" "strict"]; + default = "relaxed"; + example = "strict"; + description = "SPF Identifier Alignment mode"; + apply = builtins.substring 0 1; + }; + fo = mkOption { + type = types.listOf (types.enum ["0" "1" "d" "s"]); + default = ["0"]; + example = ["0" "1" "s"]; + description = "Failure reporting options"; + apply = lib.concatStringsSep ":"; + }; + p = mkOption { + type = types.enum ["none" "quarantine" "reject"]; + example = "quarantine"; + description = "Requested Mail Receiver policy"; + }; + pct = mkOption { + type = types.ints.between 0 100; + default = 100; + example = 30; + description = "Percentage of messages to which the DMARC policy is to be applied"; + apply = builtins.toString; + }; + rf = mkOption { + type = types.listOf (types.enum ["afrf"]); + default = ["afrf"]; + example = ["afrf"]; + description = "Format to be used for message-specific failure reports"; + apply = lib.concatStringsSep ":"; + }; + ri = mkOption { + type = types.ints.unsigned; # FIXME: u32 + default = 86400; + example = 12345; + description = "Interval requested between aggregate reports"; + apply = builtins.toString; + }; + rua = mkOption { + type = types.oneOf [types.str (types.listOf types.str)]; + default = []; + example = "mailto:dmarc+rua@example.com"; + description = "Addresses to which aggregate feedback is to be sent"; + apply = val: + # FIXME: need to encode commas in URIs + if builtins.isList val + then lib.concatStringsSep "," val + else val; + }; + ruf = mkOption { + type = types.listOf types.str; + default = []; + example = ["mailto:dmarc+ruf@example.com" "mailto:another+ruf@example.com"]; + description = "Addresses to which message-specific failure information is to be reported"; + apply = val: + # FIXME: need to encode commas in URIs + if builtins.isList val + then lib.concatStringsSep "," val + else val; + }; + sp = mkOption { + type = types.nullOr (types.enum ["none" "quarantine" "reject"]); + default = null; + example = "quarantine"; + description = "Requested Mail Receiver policy for all subdomains"; + }; + }; + dataToString = data: let + # The specification could be more clear on this, but `v` and `p` MUST + # be the first two tags in the record. + items = + ["v=DMARC1; p=${data.p}"] + ++ lib.pipe data [ + (builtins.intersectAttrs options) # remove garbage list `_module` + (lib.filterAttrs (k: v: v != null && v != "" && k != "p")) + (lib.mapAttrsToList (k: v: "${k}=${v}")) + ]; + result = lib.concatStringsSep "; " items + ";"; + in + util.writeCharacterString result; + nameFixup = name: _self: "_dmarc.${name}"; +} diff --git a/modules/by-name/dn/dns/dns/types/records/DNAME.nix b/modules/by-name/dn/dns/dns/types/records/DNAME.nix new file mode 100644 index 0000000..042ce95 --- /dev/null +++ b/modules/by-name/dn/dns/dns/types/records/DNAME.nix @@ -0,0 +1,15 @@ +# RFC 6672 +{lib, ...}: let + inherit (lib) dns mkOption; +in { + rtype = "DNAME"; + options = { + dname = mkOption { + type = dns.types.domain-name; + example = "www.test.com"; + description = "A <domain-name> which provides redirection from a part of the DNS name tree to another part of the DNS name tree"; + }; + }; + dataToString = {dname, ...}: "${dname}"; + fromString = dname: {inherit dname;}; +} diff --git a/modules/by-name/dn/dns/dns/types/records/DNSKEY.nix b/modules/by-name/dn/dns/dns/types/records/DNSKEY.nix new file mode 100644 index 0000000..86ce3a1 --- /dev/null +++ b/modules/by-name/dn/dns/dns/types/records/DNSKEY.nix @@ -0,0 +1,63 @@ +# SPDX-FileCopyrightText: 2020 Aluísio Augusto Silva Gonçalves <https://aasg.name> +# +# SPDX-License-Identifier: MPL-2.0 or MIT +# RFC 4034, 2 +{lib, ...}: let + inherit (builtins) isInt split; + inherit (lib) concatStrings flatten mkOption types; + + dnssecOptions = import ./dnssec.nix {inherit lib;}; + inherit (dnssecOptions) mkDNSSECAlgorithmOption; +in { + rtype = "DNSKEY"; + options = { + flags = mkOption { + description = "Flags pertaining to this RR."; + type = types.either types.ints.u16 (types.submodule { + options = { + zoneSigningKey = mkOption { + description = "Whether this RR holds a zone signing key (ZSK)."; + type = types.bool; + default = false; + }; + secureEntryPoint = mkOption { + type = types.bool; + description = '' + Whether this RR holds a secure entry point. + In general, this means the key is a key-signing key (KSK), as opposed to a zone-signing key. + ''; + default = false; + }; + }; + }); + apply = value: + if isInt value + then value + else + ( + if value.zoneSigningKey + then 256 + else 0 + ) + + ( + if value.secureEntryPoint + then 1 + else 0 + ); + }; + algorithm = mkDNSSECAlgorithmOption { + description = "Algorithm of the key referenced by this RR."; + }; + publicKey = mkOption { + type = types.str; + description = "Base64-encoded public key."; + apply = value: concatStrings (flatten (split "[[:space:]]" value)); + }; + }; + dataToString = { + flags, + algorithm, + publicKey, + ... + }: "${toString flags} 3 ${toString algorithm} ${publicKey}"; +} diff --git a/modules/by-name/dn/dns/dns/types/records/DS.nix b/modules/by-name/dn/dns/dns/types/records/DS.nix new file mode 100644 index 0000000..76fac9a --- /dev/null +++ b/modules/by-name/dn/dns/dns/types/records/DS.nix @@ -0,0 +1,48 @@ +# SPDX-FileCopyrightText: 2020 Aluísio Augusto Silva Gonçalves <https://aasg.name> +# +# SPDX-License-Identifier: MPL-2.0 or MIT +# RFC 4034, 5 +{lib, ...}: let + inherit (lib) mkOption types; + + dnssecOptions = import ./dnssec.nix {inherit lib;}; + inherit (dnssecOptions) mkRegisteredNumberOption mkDNSSECAlgorithmOption; + + mkDSDigestTypeOption = args: + mkRegisteredNumberOption { + registryName = "Delegation Signer (DS) Resource Record (RR) Type Digest Algorithms"; + numberType = types.ints.u8; + # These mnemonics are unofficial, unlike the DNSSEC algorithm ones. + mnemonics = { + "sha-1" = 1; + "sha-256" = 2; + "gost" = 3; + "sha-384" = 4; + }; + }; +in { + rtype = "DS"; + options = { + keyTag = mkOption { + description = "Tag computed over the DNSKEY referenced by this RR to identify it."; + type = types.ints.u16; + }; + algorithm = mkDNSSECAlgorithmOption { + description = "Algorithm of the key referenced by this RR."; + }; + digestType = mkDSDigestTypeOption { + description = "Type of the digest given in the `digest` attribute."; + }; + digest = mkOption { + description = "Digest of the DNSKEY referenced by this RR."; + type = types.strMatching "[[:xdigit:]]+"; + }; + }; + dataToString = { + keyTag, + algorithm, + digestType, + digest, + ... + }: "${toString keyTag} ${toString algorithm} ${toString digestType} ${digest}"; +} diff --git a/modules/by-name/dn/dns/dns/types/records/HTTPS.nix b/modules/by-name/dn/dns/dns/types/records/HTTPS.nix new file mode 100644 index 0000000..6e2ef3d --- /dev/null +++ b/modules/by-name/dn/dns/dns/types/records/HTTPS.nix @@ -0,0 +1,5 @@ +args: +import ./SVCB.nix args +// { + rtype = "HTTPS"; +} diff --git a/modules/by-name/dn/dns/dns/types/records/MTA-STS.nix b/modules/by-name/dn/dns/dns/types/records/MTA-STS.nix new file mode 100644 index 0000000..030490e --- /dev/null +++ b/modules/by-name/dn/dns/dns/types/records/MTA-STS.nix @@ -0,0 +1,42 @@ +# +# SPDX-FileCopyrightText: 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +# +# SPDX-License-Identifier: MPL-2.0 or MIT +# +# This is a “fake” record type, not actually part of DNS. +# It gets compiled down to a TXT record. +# RFC 8461 +{ + lib, + util, + ... +}: let + inherit (lib) mkOption types; +in rec { + rtype = "TXT"; + options = { + id = mkOption { + type = types.str; + example = "20160831085700Z"; + description = '' + A short string used to track policy updates. This string MUST + uniquely identify a given instance of a policy, such that senders + can determine when the policy has been updated by comparing to the + "id" of a previously seen policy. There is no implied ordering of + "id" fields between revisions. + ''; + }; + }; + dataToString = data: let + items = + ["v=STSv1"] + ++ lib.pipe data [ + (builtins.intersectAttrs options) # remove garbage list `_module` + (lib.filterAttrs (k: v: v != null && v != "")) + (lib.mapAttrsToList (k: v: "${k}=${v}")) + ]; + result = lib.concatStringsSep "; " items + ";"; + in + util.writeCharacterString result; + nameFixup = name: _self: "_mta-sts.${name}"; +} diff --git a/modules/by-name/dn/dns/dns/types/records/MX.nix b/modules/by-name/dn/dns/dns/types/records/MX.nix new file mode 100644 index 0000000..c25b89c --- /dev/null +++ b/modules/by-name/dn/dns/dns/types/records/MX.nix @@ -0,0 +1,32 @@ +# +# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/> +# +# SPDX-License-Identifier: MPL-2.0 or MIT +# +# RFC 1035, 3.3.9 +{ + lib, + simple, + ... +}: let + inherit (lib) mkOption types; +in { + rtype = "MX"; + options = { + preference = mkOption { + type = types.ints.u16; + example = 10; + description = "The preference given to this RR among others at the same owner. Lower values are preferred"; + }; + exchange = mkOption { + type = simple.types.domain-name; + example = "smtp.example.com."; + description = "A <domain-name> which specifies a host willing to act as a mail exchange for the owner name"; + }; + }; + dataToString = { + preference, + exchange, + ... + }: "${toString preference} ${exchange}"; +} diff --git a/modules/by-name/dn/dns/dns/types/records/NS.nix b/modules/by-name/dn/dns/dns/types/records/NS.nix new file mode 100644 index 0000000..ea60a91 --- /dev/null +++ b/modules/by-name/dn/dns/dns/types/records/NS.nix @@ -0,0 +1,24 @@ +# +# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/> +# +# SPDX-License-Identifier: MPL-2.0 or MIT +# +# RFC 1035, 3.3.11 +{ + lib, + simple, + ... +}: let + inherit (lib) mkOption; +in { + rtype = "NS"; + options = { + nsdname = mkOption { + type = simple.types.domain-name; + example = "ns2.example.com"; + description = "A <domain-name> which specifies a host which should be authoritative for the specified class and domain"; + }; + }; + dataToString = {nsdname, ...}: "${nsdname}"; + fromString = nsdname: {inherit nsdname;}; +} diff --git a/modules/by-name/dn/dns/dns/types/records/OPENPGPKEY.nix b/modules/by-name/dn/dns/dns/types/records/OPENPGPKEY.nix new file mode 100644 index 0000000..1f39cb9 --- /dev/null +++ b/modules/by-name/dn/dns/dns/types/records/OPENPGPKEY.nix @@ -0,0 +1,18 @@ +# RFC7929 +{ + lib, + util, + ... +}: let + inherit (lib) mkOption types; +in { + rtype = "OPENPGPKEY"; + options = { + data = mkOption { + type = types.str; + }; + }; + + dataToString = {data, ...}: util.writeCharacterString data; + fromString = data: {inherit data;}; +} diff --git a/modules/by-name/dn/dns/dns/types/records/PTR.nix b/modules/by-name/dn/dns/dns/types/records/PTR.nix new file mode 100644 index 0000000..075f82e --- /dev/null +++ b/modules/by-name/dn/dns/dns/types/records/PTR.nix @@ -0,0 +1,92 @@ +# +# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/> +# +# SPDX-License-Identifier: MPL-2.0 or MIT +# +# RFC 1035, 3.3.12 +{ + lib, + simple, + ... +}: let + inherit (lib) mkOption; + + inherit (lib.strings) stringToCharacters splitString; + + reverseIpv4 = input: + builtins.concatStringsSep "." (lib.lists.reverseList (splitString "." + input)); + + reverseIpv6 = input: let + split = splitString ":" input; + elementLength = builtins.length split; + + reverseString = string: + builtins.concatStringsSep "" (lib.lists.reverseList + (stringToCharacters string)); + in + reverseString (builtins.concatStringsSep "." (stringToCharacters (builtins.concatStringsSep + "" (builtins.map ( + part: let + c = stringToCharacters part; + in + if builtins.length c == 4 + then + # valid part + part + else if builtins.length c < 4 && builtins.length c > 0 + then + # leading zeros were elided + (builtins.concatStringsSep "" ( + builtins.map builtins.toString ( + builtins.genList (_: 0) (4 - (builtins.length c)) + ) + )) + + part + else if builtins.length c == 0 + then + # Multiple full blocks were elided. Only one of these can be in an + # IPv6 address, as such we can simply add (8 - (elementLength - 1)) `0000` + # blocks. We need to substract one from `elementLength` because + # this empty part is included in the `elementLength`. + builtins.concatStringsSep "" (builtins.genList (_: "0000") (8 - (elementLength - 1))) + else builtins.throw "Impossible" + ) + split)))); +in { + rtype = "PTR"; + options = { + name = mkOption { + type = simple.types.domain-name; + example = "mail2.server.com"; + description = "The <domain-name> which is defined by the IP."; + }; + ip = { + v4 = mkOption { + type = lib.types.nullOr lib.types.str; + example = "192.168.1.4"; + description = "The IPv4 address of the host."; + default = null; + apply = v: + if v != null + then reverseIpv4 v + else v; + }; + v6 = mkOption { + type = lib.types.nullOr lib.types.str; + example = "192.168.1.4"; + description = "The IPv6 address of the host."; + default = null; + apply = v: + if v != null + then reverseIpv6 v + else v; + }; + }; + }; + dataToString = {name, ...}: "${name}."; + nameFixup = name: self: + if self.ip.v6 == null + then "${self.ip.v4}.in-addr.arpa" + else "${self.ip.v6}.ip6.arpa"; +} diff --git a/modules/by-name/dn/dns/dns/types/records/SOA.nix b/modules/by-name/dn/dns/dns/types/records/SOA.nix new file mode 100644 index 0000000..db7436e --- /dev/null +++ b/modules/by-name/dn/dns/dns/types/records/SOA.nix @@ -0,0 +1,65 @@ +# +# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/> +# +# SPDX-License-Identifier: MPL-2.0 or MIT +# +# RFC 1035, 3.3.13 +{ + lib, + simple, + ... +}: let + inherit (lib) concatStringsSep removeSuffix replaceStrings; + inherit (lib) mkOption types; +in { + rtype = "SOA"; + options = { + nameServer = mkOption { + type = simple.types.domain-name; + example = "ns1.example.com"; + description = "The <domain-name> of the name server that was the original or primary source of data for this zone. Don't forget the dot at the end!"; + }; + adminEmail = mkOption { + type = simple.types.domain-name; + example = "admin@example.com"; + description = "An email address of the person responsible for this zone. (Note: in traditional zone files you are supposed to put a dot instead of `@` in your address; you can use `@` with this module and it is recommended to do so. Also don't put the dot at the end!)"; + apply = s: replaceStrings ["@"] ["."] (removeSuffix "." s); + }; + serial = mkOption { + type = types.ints.unsigned; # TODO: u32 + example = 20; + description = "Version number of the original copy of the zone"; + }; + refresh = mkOption { + type = types.ints.unsigned; # TODO: u32 + default = 24 * 60 * 60; + example = 7200; + description = "Time interval before the zone should be refreshed"; + }; + retry = mkOption { + type = types.ints.unsigned; # TODO: u32 + default = 10 * 60; + example = 600; + description = "Time interval that should elapse before a failed refresh should be retried"; + }; + expire = mkOption { + type = types.ints.unsigned; # TODO: u32 + default = 10 * 24 * 60 * 60; + example = 3600000; + description = "Time value that specifies the upper limit on the time interval that can elapse before the zone is no longer authoritative"; + }; + minimum = mkOption { + type = types.ints.unsigned; # TODO: u32 + default = 60; + example = 60; + description = "Minimum TTL field that should be exported with any RR from this zone"; + }; + }; + dataToString = data @ { + nameServer, + adminEmail, + ... + }: let + numbers = map toString (with data; [serial refresh retry expire minimum]); + in "${nameServer} ${adminEmail}. (${concatStringsSep " " numbers})"; +} diff --git a/modules/by-name/dn/dns/dns/types/records/SRV.nix b/modules/by-name/dn/dns/dns/types/records/SRV.nix new file mode 100644 index 0000000..5f558ed --- /dev/null +++ b/modules/by-name/dn/dns/dns/types/records/SRV.nix @@ -0,0 +1,51 @@ +# +# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/> +# +# SPDX-License-Identifier: MPL-2.0 or MIT +# +# RFC 2782 +{ + lib, + simple, + ... +}: let + inherit (lib) mkOption types; +in { + rtype = "SRV"; + options = { + service = mkOption { + type = types.str; + example = "foobar"; + description = "The symbolic name of the desired service. Do not add the underscore!"; + }; + proto = mkOption { + type = types.str; + example = "tcp"; + description = "The symbolic name of the desired protocol. Do not add the underscore!"; + }; + priority = mkOption { + type = types.ints.u16; + default = 0; + example = 0; + description = "The priority of this target host"; + }; + weight = mkOption { + type = types.ints.u16; + default = 100; + example = 20; + description = "The weight field specifies a relative weight for entries with the same priority. Larger weights SHOULD be given a proportionately higher probability of being selected"; + }; + port = mkOption { + type = types.ints.u16; + example = 9; + description = "The port on this target host of this service"; + }; + target = mkOption { + type = simple.types.domain-name; + example = ""; + description = "The domain name of the target host"; + }; + }; + dataToString = data: with data; "${toString priority} ${toString weight} ${toString port} ${target}"; + nameFixup = name: self: "_${self.service}._${self.proto}.${name}"; +} diff --git a/modules/by-name/dn/dns/dns/types/records/SSHFP.nix b/modules/by-name/dn/dns/dns/types/records/SSHFP.nix new file mode 100644 index 0000000..1409860 --- /dev/null +++ b/modules/by-name/dn/dns/dns/types/records/SSHFP.nix @@ -0,0 +1,39 @@ +# RFC 4255 +{lib, ...}: let + inherit (lib) mkOption types; + inherit (builtins) attrNames; + algorithm = { + "rsa" = 1; + "dsa" = 2; + "ecdsa" = 3; # RFC 6594 + "ed25519" = 4; # RFC 7479 / RFC 8709 + "ed448" = 6; # RFC 8709 + }; + mode = { + "sha1" = 1; + "sha256" = 2; # RFC 6594 + }; +in { + rtype = "SSHFP"; + options = { + algorithm = mkOption { + example = "ed25519"; + type = types.enum (attrNames algorithm); + apply = value: algorithm.${value}; + }; + fingerprintType = mkOption { + example = "sha256"; + type = types.enum (attrNames mode); + apply = value: mode.${value}; + }; + fingerprint = mkOption { + type = types.str; + }; + }; + dataToString = { + algorithm, + fingerprintType, + fingerprint, + ... + }: "${toString algorithm} ${toString fingerprintType} ${fingerprint}"; +} diff --git a/modules/by-name/dn/dns/dns/types/records/SVCB.nix b/modules/by-name/dn/dns/dns/types/records/SVCB.nix new file mode 100644 index 0000000..62cbc3d --- /dev/null +++ b/modules/by-name/dn/dns/dns/types/records/SVCB.nix @@ -0,0 +1,100 @@ +# rfc9460 +{lib, ...}: let + inherit + (lib) + concatStringsSep + filter + isInt + isList + mapAttrsToList + mkOption + types + ; + + mkSvcParams = params: + concatStringsSep " " ( + filter (s: s != "") ( + mapAttrsToList ( + name: value: + if value + then name + else if isList value + then "${name}=${concatStringsSep "," value}" + else if isInt value + then "${name}=${builtins.toString value}" + else "" + ) + params + ) + ); +in { + rtype = "SVCB"; + options = { + svcPriority = mkOption { + example = 1; + type = types.ints.u16; + }; + targetName = mkOption { + example = "."; + type = types.str; + }; + mandatory = mkOption { + example = ["ipv4hint"]; + default = null; + type = types.nullOr (types.nonEmptyListOf types.str); + }; + alpn = mkOption { + example = ["h2"]; + default = null; + type = types.nullOr (types.nonEmptyListOf types.str); + }; + no-default-alpn = mkOption { + example = true; + default = false; + type = types.bool; + }; + port = mkOption { + example = 443; + default = null; + type = types.nullOr types.port; + }; + ipv4hint = mkOption { + example = ["127.0.0.1"]; + default = null; + type = types.nullOr (types.nonEmptyListOf types.str); + }; + ipv6hint = mkOption { + example = ["::1"]; + default = null; + type = types.nullOr (types.nonEmptyListOf types.str); + }; + ech = mkOption { + type = types.nullOr types.str; + default = null; + }; + }; + dataToString = { + svcPriority, + targetName, + mandatory ? null, + alpn ? null, + no-default-alpn ? null, + port ? null, + ipv4hint ? null, + ipv6hint ? null, + ech ? null, + ... + }: "${toString svcPriority} ${targetName} ${ + mkSvcParams { + inherit + alpn + ech + ipv4hint + ipv6hint + mandatory + no-default-alpn + port + ; + } + }"; +} diff --git a/modules/by-name/dn/dns/dns/types/records/TLSA.nix b/modules/by-name/dn/dns/dns/types/records/TLSA.nix new file mode 100644 index 0000000..d92a29b --- /dev/null +++ b/modules/by-name/dn/dns/dns/types/records/TLSA.nix @@ -0,0 +1,50 @@ +# RFC 6698 +{lib, ...}: let + inherit (lib) mkOption types; + inherit (builtins) attrNames; + + certUsage = { + "pkix-ta" = 0; + "pkix-ee" = 1; + "dane-ta" = 2; + "dane-ee" = 3; + }; + selectors = { + "cert" = 0; + "spki" = 1; + }; + match = { + "exact" = 0; + "sha256" = 1; + "sha512" = 2; + }; +in { + rtype = "TLSA"; + options = { + certUsage = mkOption { + example = "dane-ee"; + type = types.enum (attrNames certUsage); + apply = value: certUsage.${value}; + }; + selector = mkOption { + example = "spki"; + type = types.enum (attrNames selectors); + apply = value: selectors.${value}; + }; + matchingType = mkOption { + example = "sha256"; + type = types.enum (attrNames match); + apply = value: match.${value}; + }; + certificate = mkOption { + type = types.str; + }; + }; + dataToString = { + certUsage, + selector, + matchingType, + certificate, + ... + }: "${toString certUsage} ${toString selector} ${toString matchingType} ${certificate}"; +} diff --git a/modules/by-name/dn/dns/dns/types/records/TXT.nix b/modules/by-name/dn/dns/dns/types/records/TXT.nix new file mode 100644 index 0000000..d605ce8 --- /dev/null +++ b/modules/by-name/dn/dns/dns/types/records/TXT.nix @@ -0,0 +1,24 @@ +# +# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/> +# +# SPDX-License-Identifier: MPL-2.0 or MIT +# +# RFC 1035, 3.3.14 +{ + lib, + util, + ... +}: let + inherit (lib) mkOption types; +in { + rtype = "TXT"; + options = { + data = mkOption { + type = types.str; + example = "favorite drink=orange juice"; + description = "Arbitrary information"; + }; + }; + dataToString = {data, ...}: util.writeCharacterString data; + fromString = data: {inherit data;}; +} diff --git a/modules/by-name/dn/dns/dns/types/records/default.nix b/modules/by-name/dn/dns/dns/types/records/default.nix new file mode 100644 index 0000000..76a86cd --- /dev/null +++ b/modules/by-name/dn/dns/dns/types/records/default.nix @@ -0,0 +1,43 @@ +# +# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/> +# +# SPDX-License-Identifier: MPL-2.0 or MIT +# +{ + lib, + util, + simple, +}: let + inherit (lib.attrsets) genAttrs; + + types = [ + "A" + "AAAA" + "CAA" + "CNAME" + "DNAME" + "MX" + "NS" + "SOA" + "SRV" + "TXT" + "PTR" + + # DNSSEC types + "DNSKEY" + "DS" + + # DANE types + "SSHFP" + "TLSA" + "OPENPGPKEY" + "SVCB" + "HTTPS" + + # Pseudo types + "DKIM" + "DMARC" + "MTA-STS" + ]; +in + genAttrs types (t: import (./. + "/${t}.nix") {inherit lib simple util;}) diff --git a/modules/by-name/dn/dns/dns/types/records/dnssec.nix b/modules/by-name/dn/dns/dns/types/records/dnssec.nix new file mode 100644 index 0000000..648f676 --- /dev/null +++ b/modules/by-name/dn/dns/dns/types/records/dnssec.nix @@ -0,0 +1,48 @@ +# SPDX-FileCopyrightText: 2020 Aluísio Augusto Silva Gonçalves <https://aasg.name> +# +# SPDX-License-Identifier: MPL-2.0 or MIT +{lib}: let + inherit (builtins) attrNames isInt removeAttrs; + inherit (lib) mkOption types; +in rec { + mkRegisteredNumberOption = { + registryName, + numberType, + mnemonics, + } @ args: + mkOption + { + type = + types.either numberType (types.enum (attrNames mnemonics)) + // { + name = "registeredNumber"; + description = "number in IANA registry '${registryName}'"; + }; + apply = value: + if isInt value + then value + else mnemonics.${value}; + } + // removeAttrs args ["registryName" "numberType" "mnemonics"]; + + mkDNSSECAlgorithmOption = args: + mkRegisteredNumberOption { + registryName = "Domain Name System Security (DNSSEC) Algorithm Numbers"; + numberType = types.ints.u8; + mnemonics = { + "dsa" = 3; + "rsasha1" = 5; + "dsa-nsec3-sha1" = 6; + "rsasha1-nsec3-sha1" = 7; + "rsasha256" = 8; + "rsasha512" = 10; + "ecc-gost" = 12; + "ecdsap256sha256" = 13; + "ecdsap384sha384" = 14; + "ed25519" = 15; + "ed448" = 16; + "privatedns" = 253; + "privateoid" = 254; + }; + }; +} diff --git a/modules/by-name/dn/dns/dns/types/simple.nix b/modules/by-name/dn/dns/dns/types/simple.nix new file mode 100644 index 0000000..fece2c9 --- /dev/null +++ b/modules/by-name/dn/dns/dns/types/simple.nix @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2021 Kirill Elagin <https://kir.elagin.me/> +# +# SPDX-License-Identifier: MPL-2.0 or MIT +{lib}: let + inherit (builtins) stringLength; +in { + # RFC 1035, 3.1 + domain-name = lib.types.addCheck lib.types.str (s: stringLength s <= 255); +} diff --git a/modules/by-name/dn/dns/dns/types/zone.nix b/modules/by-name/dn/dns/dns/types/zone.nix new file mode 100644 index 0000000..44ccb15 --- /dev/null +++ b/modules/by-name/dn/dns/dns/types/zone.nix @@ -0,0 +1,119 @@ +# +# SPDX-FileCopyrightText: 2019 Kirill Elagin <https://kir.elagin.me/> +# SPDX-FileCopyrightText: 2021 Naïm Favier <n@monade.li> +# +# SPDX-License-Identifier: MPL-2.0 or MIT +# +{ + lib, + util, + simple, +}: let + inherit (builtins) filter removeAttrs; + inherit + (lib) + concatMapStringsSep + concatStringsSep + mapAttrs + mapAttrsToList + optionalString + ; + inherit (lib) mkOption literalExample types; + + inherit (import ./record.nix {inherit lib;}) recordType writeRecord; + + rsubtypes = import ./records {inherit lib util simple;}; + rsubtypes' = removeAttrs rsubtypes ["SOA"]; + + subzoneOptions = + { + subdomains = mkOption { + type = types.attrsOf subzone; + default = {}; + example = { + www = { + A = [{address = "1.1.1.1";}]; + }; + staging = { + A = [{address = "1.0.0.1";}]; + }; + }; + description = "Records for subdomains of the domain"; + }; + } + // mapAttrs (n: t: + mkOption { + type = types.listOf (recordType t); + default = []; + # example = [ t.example ]; # TODO: any way to auto-generate an example for submodule? + description = "List of ${n} records for this zone/subzone"; + }) + rsubtypes'; + + subzone = types.submodule { + options = subzoneOptions; + }; + + writeSubzone = name: zone: let + groupToString = pseudo: subt: + concatMapStringsSep "\n" (writeRecord name subt) zone."${pseudo}"; + groups = mapAttrsToList groupToString rsubtypes'; + groups' = filter (s: s != "") groups; + + writeSubzone' = subname: writeSubzone "${subname}.${name}"; + sub = concatStringsSep "\n\n" (mapAttrsToList writeSubzone' zone.subdomains); + in + concatStringsSep "\n\n" groups' + + optionalString (sub != "") ("\n\n" + sub); + zone = types.submodule ({name, ...}: { + options = + { + useOrigin = mkOption { + type = types.bool; + default = false; + description = "Wether to use $ORIGIN and unqualified name or fqdn when exporting the zone."; + }; + + TTL = mkOption { + type = types.ints.unsigned; + default = 24 * 60 * 60; + example = literalExample "60 * 60"; + description = "Default record caching duration. Sets the $TTL variable"; + }; + SOA = mkOption rec { + type = recordType rsubtypes.SOA; + example = + { + ttl = 24 * 60 * 60; + } + // type.example; + description = "SOA record"; + }; + } + // subzoneOptions; + }); + renderToString = name: { + useOrigin, + TTL, + SOA, + ... + } @ zone: + if useOrigin + then '' + $ORIGIN ${name}. + $TTL ${toString TTL} + + ${writeRecord "@" rsubtypes.SOA SOA} + + ${writeSubzone "@" zone} + '' + else '' + $TTL ${toString TTL} + + ${writeRecord name rsubtypes.SOA SOA} + + ${writeSubzone name zone} + ''; +in { + inherit zone subzone renderToString; +} diff --git a/modules/by-name/dn/dns/dns/util/default.nix b/modules/by-name/dn/dns/dns/util/default.nix new file mode 100644 index 0000000..59e661d --- /dev/null +++ b/modules/by-name/dn/dns/dns/util/default.nix @@ -0,0 +1,76 @@ +# SPDX-FileCopyrightText: 2021 Kirill Elagin <https://kir.elagin.me/> +# +# SPDX-License-Identifier: MPL-2.0 or MIT +{lib}: let + inherit + (builtins) + concatStringsSep + genList + stringLength + substring + ; + inherit + (lib.strings) + concatMapStrings + concatMapStringsSep + fixedWidthString + splitString + stringToCharacters + ; + inherit (lib.lists) filter reverseList; + + /* + Split a string into byte chunks, such that each output String is less then or equal to + `n` bytes. + + # Type + + splitInGroupsOf :: Integer -> String -> [String] + + # Arguments + + n + : The number of bytes to put into each String. + + s + : The String to split. + */ + splitInGroupsOf = n: s: let + groupCount = (stringLength s - 1) / n + 1; + in + genList (i: substring (i * n) n s) groupCount; + + # : str -> str + # Prepares a Nix string to be written to a zone file as a character-string + # literal: breaks it into chunks of 255 (per RFC 1035, 3.3) and encloses + # each chunk in quotation marks. + writeCharacterString = s: + if stringLength s <= 255 + then ''"${s}"'' + else concatMapStringsSep " " (x: ''"${x}"'') (splitInGroupsOf 255 s); + + # : str -> str, with length 4 (zeros are padded to the left) + align4Bytes = fixedWidthString 4 "0"; + + # : int -> str -> str + # Expands "" to 4n zeros and aligns the rest on 4 bytes + align4BytesOrExpand = n: v: + if v == "" + then (fixedWidthString (4 * n) "0" "") + else align4Bytes v; + + # : str -> [ str ] + # Returns the record of the ipv6 as a list + mkRecordAux = v6: let + splitted = splitString ":" v6; + n = 8 - builtins.length (filter (x: x != "") splitted); + in + stringToCharacters (concatMapStrings (align4BytesOrExpand n) splitted); + + # : str -> str + # Returns the reversed record of the ipv6 + mkReverseRecord = v6: + concatStringsSep "." (reverseList (mkRecordAux v6)) + ".ip6.arpa"; +in { + inherit writeCharacterString mkReverseRecord; +} diff --git a/modules/by-name/dn/dns/module.nix b/modules/by-name/dn/dns/module.nix new file mode 100644 index 0000000..8f4ad37 --- /dev/null +++ b/modules/by-name/dn/dns/module.nix @@ -0,0 +1,86 @@ +{ + config, + lib, + ... +}: let + cfg = config.vhack.dns; + + zones = + builtins.mapAttrs (name: value: { + data = + dns.types.zone.renderToString name value; + }) + cfg.zones; + + dns = import ./dns {inherit lib;}; + + ports = let + parsePorts = listeners: let + splitAddress = addr: lib.splitString "@" addr; + + extractPort = addr: let + split = splitAddress addr; + in + lib.toInt ( + if (builtins.length split) == 2 + then builtins.elemAt split 1 + else "53" + ); + in + builtins.map extractPort listeners; + in + lib.unique (parsePorts cfg.interfaces); +in { + options.vhack.dns = { + enable = lib.mkEnableOption "custom dns server"; + + openFirewall = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Open the following ports: + TCP (${lib.concatStringsSep ", " (map toString ports)}) + UDP (${lib.concatStringsSep ", " (map toString ports)}) + ''; + }; + + interfaces = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = '' + A list of the interfaces to bind to. To select the port add `@` to the end of the + interface. The default port is 53. + ''; + example = [ + "192.168.1.3" + "2001:db8:1::3" + ]; + }; + + zones = lib.mkOption { + type = lib.types.attrsOf dns.types.zone.zone; + description = "DNS zones"; + }; + }; + + config = lib.mkIf cfg.enable { + services.nsd = { + enable = true; + verbosity = 4; + inherit (cfg) interfaces; + inherit zones; + }; + + networking.firewall.allowedUDPPorts = lib.mkIf cfg.openFirewall ports; + networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall ports; + + systemd.services.nsd = { + requires = [ + "network-online.target" + ]; + after = [ + "network.target" + "network-online.target" + ]; + }; + }; +} diff --git a/modules/by-name/et/etesync/module.nix b/modules/by-name/et/etesync/module.nix index 0f6c565..bcabc8a 100644 --- a/modules/by-name/et/etesync/module.nix +++ b/modules/by-name/et/etesync/module.nix @@ -9,6 +9,10 @@ in { enable = lib.mkEnableOption '' a secure, end-to-end encrypted, and privacy respecting sync for your contacts, calendars, tasks and notes. ''; + secretFile = lib.mkOption { + type = lib.types.path; + description = "The age encrypted globale etebase secretfile passed to agenix"; + }; }; config = lib.mkIf cfg.enable { @@ -25,13 +29,13 @@ in { }; age.secrets.etebase-server = { - file = ./secret_file.age; + file = cfg.secretFile; mode = "700"; owner = "etebase-server"; group = "etebase-server"; }; - environment.persistence."/srv".directories = [ + vhack.persist.directories = [ { directory = "/var/lib/etebase-server"; user = "etebase-server"; @@ -68,5 +72,9 @@ in { }; }; }; + users = { + users.etebase-server.uid = config.vhack.constants.ids.uids.etebase-server; + groups.etebase-server.gid = config.vhack.constants.ids.gids.etebase-server; + }; }; } diff --git a/modules/by-name/et/etesync/secret_file.age b/modules/by-name/et/etesync/secret_file.age deleted file mode 100644 index 8d8e3c2..0000000 --- a/modules/by-name/et/etesync/secret_file.age +++ /dev/null @@ -1,17 +0,0 @@ ------BEGIN AGE ENCRYPTED FILE----- -YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0UiswNDhQNWpsaFZUQTdY -U3F2TFlrSzhMbmRBWEIyTGQ2VGVramdPTDI4CjRGSnlqUm5rWWJ2Vk5neE56azdt -WitpbXlPWngxSGtEalBKWkRZdHF5QjQKLT4gWDI1NTE5IDRSSW1jcHhocjBIM0tM -ZjRxNUhZWkhkd1c5aVlucTMxTTVhSHRIMHMyU0EKbWlQZ0xKRXUvOWluSkZQRWdp -UjNMQWR3MHNwbUVYbm4vSGJQOGtrb2ZxVQotPiBzc2gtZWQyNTUxOSBPRDhUNGcg -SEpCY1JWZm5yMG1lL3QwUERPVUFqRWo5ZVJEb1JqNGVLS3pXVkhaYk1SYwpjb3dW -UWcrMkdmYTlvckFOYmsvcGwvY1dvc1oxY1FaY2p4eURCK3BIR044Ci0+ICgreWhl -KG9RLWdyZWFzZSAobEpLXVEgNVA3IGQKekx5YVFkeFRBUlJiUis2cFVyWlBPNncK -LS0tIFJxa0hDZUIyYm5uYlhiZjRnNHRLNTRrRW01d1hCL2dCZnByL1M2SkFyQXMK -gsR7erKGQrBhXlcnR73PbnC+PzOQlsBOg6a6DosGyixbnEgZ4DfyeK5Ep1oPB81Q -zcS9AV7h+8NlpmVM4G+0JCIC8I3TTCEQyOPwiu+GVXr4GYy/3stg+pK1htkt2V2M -WraPl//K3kvFln1KRt5lbsVXLX8SYZS4UJDzK25oJElwdNuqXHqwMkTmXjEgnbvS -pjgaNak5ooxHiZfCtzismLx5iL+P/+oohegUPvW16fQTq/eKp3mIjeBZmrWNnTuL -/xlhk0vp0+jS3+TqgGWSwAAqoCp/+TewUZ9f+GhU0/pkU3HP4+tx35rKN2wxerQj -nMbQ8SphigUeMpc501oDRw6X5ZAasoww ------END AGE ENCRYPTED FILE----- diff --git a/modules/by-name/ga/gallery/module.nix b/modules/by-name/ga/gallery/module.nix new file mode 100644 index 0000000..a5237e6 --- /dev/null +++ b/modules/by-name/ga/gallery/module.nix @@ -0,0 +1,22 @@ +{ + config, + lib, + ... +}: let + cfg = config.sils.gallery; +in { + options.sils.gallery = { + enable = lib.mkEnableOption "a stateful static gallery site"; + domain = lib.mkOption { + type = lib.types.str; + }; + }; + config = lib.mkIf cfg.enable { + vhack.nginx.enable = true; + services.nginx.virtualHosts."${cfg.domain}" = { + forceSSL = true; + enableACME = true; + root = "/srv/${cfg.domain}"; + }; + }; +} diff --git a/modules/by-name/in/invidious-router/module.nix b/modules/by-name/in/invidious-router/module.nix new file mode 100644 index 0000000..f85a06c --- /dev/null +++ b/modules/by-name/in/invidious-router/module.nix @@ -0,0 +1,70 @@ +{ + config, + lib, + pkgsUnstable, + ... +}: let + cfg = config.vhack.invidious-router; +in { + options.vhack.invidious-router = { + enable = lib.mkEnableOption "invidious-router"; + domain = lib.mkOption { + type = lib.types.str; + description = "The domain invidious-router should be served on"; + }; + extraDomains = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = []; + description = "Addtional domains invidious-router should be served on"; + }; + }; + config = lib.mkIf cfg.enable { + services.invidious-router = { + enable = true; + package = pkgsUnstable.invidious-router; + settings = { + app = { + listen = "127.0.0.1:8050"; + enable_youtube_fallback = false; + reload_instance_list_interval = "60s"; + not_available_message = '' + No available invidious instance found! + [link]View this video on YouTube[/link], a proprietary + platform that collects and uses your data without respecting + your privacy. + ''; + }; + api = { + enabled = true; + url = "https://api.invidious.io/instances.json"; + filter_regions = false; + allowed_regions = [ + "AT" + "DE" + "CH" + ]; + }; + healthcheck = { + path = "/watch?v=uSvJaYxRoB4"; + allowed_status_codes = [ + 200 + ]; + timeout = "1s"; + interval = "10s"; + filter_by_response_time = { + enabled = true; + qty_of_top_results = 4; + }; + minimum_ratio = 0.2; + remove_no_ratio = false; + text_not_present = "YouTube is currently trying to block Invidious instances"; + }; + }; + nginx = { + enable = true; + inherit (cfg) domain extraDomains; + }; + }; + vhack.nginx.enable = true; + }; +} diff --git a/modules/by-name/ma/mail/module.nix b/modules/by-name/ma/mail/module.nix new file mode 100644 index 0000000..55f2fb8 --- /dev/null +++ b/modules/by-name/ma/mail/module.nix @@ -0,0 +1,167 @@ +{ + config, + lib, + ... +}: let + cfg = config.vhack.mail; + all_admins = [ + "sils@vhack.eu" + "soispha@vhack.eu" + "nightingale@vhack.eu" + ]; +in { + options.vhack.mail = { + enable = lib.mkEnableOption "sophisticated mail setup with simple-nixos-mailserver"; + fqdn = lib.mkOption { + type = lib.types.str; + description = "The fqdn mailserver should be served on."; + }; + }; + + config = lib.mkIf cfg.enable { + vhack.persist.directories = [ + { + directory = "/var/lib/mail/backup"; + user = "virtualMail"; + group = "virtualMail"; + mode = "0700"; + } + { + directory = "/var/lib/mail/sieve"; + user = "virtualMail"; + group = "virtualMail"; + mode = "0700"; + } + { + directory = "/var/lib/mail/vmail"; + user = "virtualMail"; + group = "virtualMail"; + mode = "0700"; + } + { + directory = "/var/lib/mail/dkim"; + user = "opendkim"; + group = "opendkim"; + mode = "0700"; + } + { + directory = "/var/lib/postfix/data"; + user = "postfix"; + group = "postfix"; + mode = "0700"; + } + { + directory = "/var/lib/postfix/queue"; + user = "postfix"; + group = "postfix"; + mode = "0700"; + } + { + directory = "/var/lib/rspamd"; + user = "rspamd"; + group = "rspamd"; + mode = "0700"; + } + ]; + vhack.nginx.enable = true; + security.acme.certs = { + "${cfg.fqdn}" = { + domain = cfg.fqdn; + }; + }; + mailserver = { + enable = true; + inherit (cfg) fqdn; + + useFsLayout = true; + + extraVirtualAliases = { + "abuse@vhack.eu" = all_admins; + "postmaster@vhack.eu" = all_admins; + "admin@vhack.eu" = all_admins; + }; + + mailDirectory = "/var/lib/mail/vmail"; + dkimKeyDirectory = "/var/lib/mail/dkim"; + sieveDirectory = "/var/lib/mail/sieve"; + backup.snapshotRoot = "/var/lib/mail/backup"; + + enableImap = false; + enableImapSsl = true; + enablePop3 = false; + enablePop3Ssl = true; + # SMTP + enableSubmission = false; + enableSubmissionSsl = true; + openFirewall = true; + + keyFile = "/var/lib/acme/${cfg.fqdn}/key.pem"; + certificateScheme = "acme"; + certificateFile = "/var/lib/acme/${cfg.fqdn}/fullchain.pem"; + + domains = [ + "vhack.eu" + + "s-schoeffel.de" + "b-peetz.de" + + "sils.li" + "nightingale.sils.li" + "sils.sils.li" + ]; + + loginAccounts = { + "sils@vhack.eu" = { + hashedPassword = "$2b$05$RW/Svgk7iGxvP5W7ZwUZ1e.a3fj4fteevb2MtfFYYD0d1DQ17y9Fm"; + }; + "soispha@vhack.eu" = { + hashedPassword = "$2b$05$XX36sJuHNbTFvi8DFldscOeQBHahluSkiUqD9QGzQaET7NJusSuQW"; + }; + + "benedikt.peetz@b-peetz.de" = { + hashedPassword = "$2b$05$MfET8utot2OolPZNASqoDe4VXNoG2chnEWhdfQ2E92mit0TvI2gBy"; + aliases = ["@b-peetz.de"]; + }; + "silas.schoeffel@s-schoeffel.de" = { + hashedPassword = "$2b$05$Qb8rl7ncpCcTbsSdsduJBuOITp8RTD6sfOTjuxJsVtD9vjAYY9n8e"; + aliases = ["@s-schoeffel.de"]; + }; + + "nightingale@vhack.eu" = { + hashedPassword = "$2b$05$nDKVVq1EktKXWqGFhnOLP.plLovXFyvWSuptK9GIkxA5DScKFx6YS"; + aliases = [ + "@nightingale.sils.li" + ]; + }; + "sils@sils.li" = { + hashedPassword = "$2b$05$Ebzh2ZhuWkz1p4tqJ172IejNZg10FtCxPDY4k6umYrpirXg7ezIRq"; + aliases = [ + "@sils.sils.li" + "@sils.li" + ]; + }; + + # Mail-Account used by hosted software + "mastodon@vhack.eu" = { + hashedPassword = "$2b$05$pSby3x2p3cHg0FyAE8IiJ.nYUqtAIR10JA8HNpHwMAiLXqc.ltSK."; + }; + "peertube@vhack.eu" = { + hashedPassword = "$y$j9T$hyWQ8Awd2Xrc6qsK.2hwE1$LxACfaeW.yHGbkQL95dWtID9.zXL/aMwT6lp.yU/0g0"; + }; + }; + }; + + users = { + users = { + knot-resolver.uid = config.vhack.constants.ids.uids.knot-resolver; + redis-rspamd.uid = config.vhack.constants.ids.uids.redis-rspamd; + rspamd.uid = config.vhack.constants.ids.uids.rspamd; + }; + groups = { + knot-resolver.gid = lib.mkForce config.vhack.constants.ids.gids.knot-resolver; + redis-rspamd.gid = config.vhack.constants.ids.gids.redis-rspamd; + rspamd.gid = config.vhack.constants.ids.gids.rspamd; + }; + }; + }; +} diff --git a/modules/by-name/ma/mastodon/module.nix b/modules/by-name/ma/mastodon/module.nix new file mode 100644 index 0000000..895428d --- /dev/null +++ b/modules/by-name/ma/mastodon/module.nix @@ -0,0 +1,123 @@ +{ + config, + pkgs, + lib, + ... +}: let + emailAddress = "mastodon@vhack.eu"; + applyPatches = pkg: + pkg.overrideAttrs (attrs: { + patches = (attrs.patches or []) ++ [./patches/0001-feat-treewide-Increase-character-limit-to-5000-in-me.patch]; + }); + cfg = config.vhack.mastodon; +in { + options.vhack.mastodon = { + enable = lib.mkEnableOption "a mastodon instance"; + domain = lib.mkOption { + type = lib.types.str; + description = "The Domain mastodon should be served on"; + example = "mastodon.vhack.eu"; + }; + enableTLD = lib.mkEnableOption "using the tld as handle, configured via + webfinger (note: this requires the tld to point to the same server as domain)"; + tld = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "vhack.eu"; + }; + mailPwFile = lib.mkOption { + type = lib.types.path; + description = "The age encrypted mail password file passed to agenix"; + }; + }; + config = lib.mkIf cfg.enable { + age.secrets.mastodonMail = { + file = cfg.mailPwFile; + mode = "700"; + owner = "mastodon"; + group = "mastodon"; + }; + vhack.persist.directories = [ + { + directory = "/var/lib/mastodon"; + user = "mastodon"; + group = "mastodon"; + mode = "0700"; + } + ]; + + vhack.postgresql.enable = true; + services.mastodon = { + enable = true; + + package = applyPatches pkgs.mastodon; + + # Unstable Mastodon package, used if + # security updates aren't backported. + #package = applyPatches pkgs-unstable.mastodon; + + localDomain = + if cfg.enableTLD + then cfg.tld + else cfg.domain; + smtp = { + authenticate = true; + createLocally = false; + fromAddress = emailAddress; + user = emailAddress; + host = "mail.foss-syndicate.org"; + passwordFile = config.age.secrets.mastodonMail.path; + }; + streamingProcesses = 3; # Number of Cores - 1 + extraConfig = { + WEB_DOMAIN = cfg.domain; + EMAIL_DOMAIN_ALLOWLIST = "vhack.eu|sils.li"; + }; + }; + + vhack.nginx.enable = true; + services.nginx = { + enable = true; + recommendedProxySettings = true; # required for redirections to work + virtualHosts = { + "${cfg.domain}" = { + root = "${config.services.mastodon.package}/public/"; + # mastodon only supports https, but you can override this if you offload tls elsewhere. + forceSSL = true; + enableACME = true; + + locations = { + "/system/".alias = "/var/lib/mastodon/public-system/"; + "/".tryFiles = "$uri @proxy"; + "@proxy" = { + proxyPass = "http://unix:/run/mastodon-web/web.socket"; + proxyWebsockets = true; + }; + "/api/v1/streaming/" = { + proxyPass = "http://unix:/run/mastodon-streaming/streaming.socket"; + proxyWebsockets = true; + }; + }; + }; + "${cfg.tld}" = + if cfg.enableTLD + then { + locations."/.well-known/webfinger".return = "301 https://${cfg.domain}$request_uri"; + } + else {}; + }; + }; + + users = { + users.mastodon.uid = config.vhack.constants.ids.uids.mastodon; + users.redis-mastodon.uid = config.vhack.constants.ids.uids.redis-mastodon; + groups.redis-mastodon.gid = config.vhack.constants.ids.gids.redis-mastodon; + groups.mastodon = { + gid = config.vhack.constants.ids.gids.mastodon; + members = [ + config.services.nginx.user + ]; + }; + }; + }; +} diff --git a/system/services/mastodon/patches/0001-feat-treewide-Increase-character-limit-to-5000-in-me.patch b/modules/by-name/ma/mastodon/patches/0001-feat-treewide-Increase-character-limit-to-5000-in-me.patch index 35dc809..35dc809 100644 --- a/system/services/mastodon/patches/0001-feat-treewide-Increase-character-limit-to-5000-in-me.patch +++ b/modules/by-name/ma/mastodon/patches/0001-feat-treewide-Increase-character-limit-to-5000-in-me.patch diff --git a/modules/by-name/ma/matrix/module.nix b/modules/by-name/ma/matrix/module.nix new file mode 100644 index 0000000..4b730da --- /dev/null +++ b/modules/by-name/ma/matrix/module.nix @@ -0,0 +1,171 @@ +{ + config, + pkgs, + lib, + ... +}: let + cfg = config.vhack.matrix; + clientConfig."m.homeserver".base_url = "https://${cfg.fqdn}"; + serverConfig."m.server" = "${cfg.fqdn}:443"; + mkWellKnown = data: '' + add_header Content-Type application/json; + add_header Access-Control-Allow-Origin *; + return 200 '${builtins.toJSON data}'; + ''; +in { + options.vhack.matrix = { + enable = lib.mkEnableOption "matrix setup based on synapse"; + fqdn = lib.mkOption { + type = lib.types.str; + description = "The FQDN on which matrix-synapse should be served."; + example = "matrix.vhack.eu"; + }; + url = lib.mkOption { + type = lib.types.str; + description = "The url the matrix-server should be known under."; + }; + sharedSecretFile = lib.mkOption { + type = lib.types.path; + description = "The age encrypted shared secret file for synapse, passed to agenix"; + }; + }; + config = lib.mkIf cfg.enable { + age.secrets.matrix-synapse_registration_shared_secret = { + file = cfg.sharedSecretFile; + mode = "700"; + owner = "matrix-synapse"; + group = "matrix-synapse"; + }; + networking.firewall.allowedTCPPorts = [80 443]; + + vhack.persist.directories = [ + { + directory = "/var/lib/matrix"; + user = "matrix-synapse"; + group = "matrix-synapse"; + mode = "0700"; + } + { + directory = "/var/lib/mautrix-whatsapp"; + user = "mautrix-whatsapp"; + group = "matrix-synapse"; + mode = "0750"; + } + ]; + systemd.tmpfiles.rules = [ + "d /etc/matrix 0755 matrix-synapse matrix-synapse" + ]; + + vhack.postgresql.enable = true; + vhack.nginx.enable = true; + + services = { + postgresql = { + enable = true; + initialScript = pkgs.writeText "synapse-init.sql" '' + --Matrix: + CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse'; + CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse" + TEMPLATE template0 + LC_COLLATE = "C" + LC_CTYPE = "C"; + + --Whatsapp-bridge: + CREATE ROLE "mautrix-whatsapp" WITH LOGIN PASSWORD 'whatsapp'; + CREATE DATABASE "mautrix-whatsapp" WITH OWNER "mautrix-whatsapp" + TEMPLATE template0 + LC_COLLATE = "C" + LC_CTYPE = "C"; + ''; + }; + + nginx = { + enable = true; + recommendedTlsSettings = true; + recommendedOptimisation = true; + recommendedGzipSettings = true; + recommendedProxySettings = true; + virtualHosts = { + "${cfg.url}" = { + enableACME = true; + forceSSL = true; + locations = { + "/.well-known/matrix/server".extraConfig = mkWellKnown serverConfig; + "/.well-known/matrix/client".extraConfig = mkWellKnown clientConfig; + }; + }; + "${cfg.fqdn}" = { + enableACME = true; + forceSSL = true; + locations = { + "/".return = "404"; + "/_matrix".proxyPass = "http://[::1]:8008"; + "/_synapse/client".proxyPass = "http://[::1]:8008"; + }; + }; + }; + }; + + mautrix-whatsapp = { + # FIXME(@bpeetz): This was disabled because `mautrix-whatsapp` dependends on libolm. + # Re-enable it, when this has changed. <2024-09-06> + enable = false; + settings = { + appservice = { + database = { + type = "postgres"; + uri = "postgres:///mautrix-whatsapp?host=/run/postgresql"; + }; + whatsapp = { + # TODO: See https://github.com/tulir/whatsmeow/blob/efc632c008604016ddde63bfcfca8de4e5304da9/binary/proto/def.proto#L43-L64 for a list. + # This also determines the WhatsApp icon + browser_name = "unknown"; + }; + }; + homeserver.address = "https://${cfg.fqdn}"; + bridge.permissions = { + "@soispha:vhack.eu" = "admin"; + "@sils:vhack.eu" = "admin"; + "@nightingale:vhack.eu" = "admin"; + }; + }; + }; + + matrix-synapse = { + enable = true; + dataDir = "/var/lib/matrix"; + configFile = "/etc/matrix/matrix.conf"; + settings = { + media_store_path = "/var/lib/matrix/media_store"; + registration_shared_secret_path = "${config.age.secrets.matrix-synapse_registration_shared_secret.path}"; + server_name = cfg.url; + listeners = [ + { + port = 8008; + bind_addresses = ["::1"]; + type = "http"; + tls = false; + x_forwarded = true; + resources = [ + { + names = ["client" "federation"]; + compress = true; + } + ]; + } + ]; + }; + }; + }; + users = { + users = { + matrix-synapse.uid = config.vhack.constants.ids.uids.matrix-synapse; + mautrix-whatsapp = { + uid = config.vhack.constants.ids.uids.mautrix-whatsapp; + group = "matrix-synapse"; + }; + }; + groups.matrix-synapse.gid = config.vhack.constants.ids.gids.matrix-synapse; + }; + }; +} diff --git a/modules/by-name/mi/miniflux/module.nix b/modules/by-name/mi/miniflux/module.nix new file mode 100644 index 0000000..0075bca --- /dev/null +++ b/modules/by-name/mi/miniflux/module.nix @@ -0,0 +1,55 @@ +{ + config, + lib, + ... +}: let + cfg = config.vhack.miniflux; +in { + options.vhack.miniflux = { + enable = lib.mkEnableOption "miniflux, an simple web rss reading software"; + domain = lib.mkOption { + type = lib.types.str; + description = "The primary domain miniflux should be served on"; + }; + extraDomains = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "Additional domains to serve miniflux on"; + default = []; + }; + adminCredentialsFile = lib.mkOption { + type = lib.types.path; + description = "The age encrypted admin credentials file passed to agenix"; + }; + }; + config = lib.mkIf cfg.enable { + age.secrets = { + minifluxAdmin = { + file = cfg.adminCredentialsFile; + mode = "700"; + owner = "root"; + group = "root"; + }; + }; + services.miniflux = { + enable = true; + config = { + LISTEN_ADDR = "127.0.0.1:5892"; + }; + adminCredentialsFile = config.age.secrets.minifluxAdmin.path; + }; + + vhack = { + nginx.enable = true; + postgresql.enable = true; + }; + services.nginx = { + virtualHosts.${cfg.domain} = { + locations."/".proxyPass = "http://${config.services.miniflux.config.LISTEN_ADDR}"; + + enableACME = true; + forceSSL = true; + serverAliases = cfg.extraDomains; + }; + }; + }; +} diff --git a/modules/by-name/mu/murmur/module.nix b/modules/by-name/mu/murmur/module.nix new file mode 100644 index 0000000..5cc6f7d --- /dev/null +++ b/modules/by-name/mu/murmur/module.nix @@ -0,0 +1,80 @@ +{ + config, + lib, + ... +}: let + cfg = config.vhack.murmur; +in { + options.vhack.murmur = { + enable = lib.mkEnableOption "murmur, a mumble server software"; + murmurStore = lib.mkOption { + type = lib.types.str; + default = "/var/lib/murmur"; + description = "The location of murmurs data dir."; + }; + host = lib.mkOption { + type = lib.types.str; + description = "The domain murmur should be served on."; + example = "mumble.vhack.eu"; + }; + url = lib.mkOption { + type = lib.types.str; + description = "The url this instance should be registered under. Note that + this is not the domain mumur is served on"; + example = "vhack.eu"; + }; + name = lib.mkOption { + type = lib.types.str; + description = "The name this instance should be registered under."; + example = "vhack"; + }; + }; + config = lib.mkIf cfg.enable { + vhack.persist.directories = [ + { + directory = cfg.murmurStore; + user = "murmur"; + group = "murmur"; + mode = "0700"; + } + ]; + + services.murmur = { + enable = true; + openFirewall = true; + welcometext = '' + <b>You never get a second chance to make a first impression</b><br> + + The entire team of [name of the company] is thrilled to welcome you on board. We hope you’ll do some amazing work here! + ''; + sslKey = "${cfg.murmurStore}/key.pem"; + sslCert = "${cfg.murmurStore}/fullchain.pem"; + + registerUrl = cfg.url; + registerName = cfg.name; + registerHostname = cfg.host; + hostName = cfg.host; + clientCertRequired = true; + bandwidth = 7200000; + }; + + security.acme.certs.murmur = { + domain = cfg.host; + postRun = + /* + bash + */ + '' + set -x + rm "${cfg.murmurStore}/key.pem" + rm "${cfg.murmurStore}/fullchain.pem" + + cp key.pem "${cfg.murmurStore}"; + cp fullchain.pem "${cfg.murmurStore}"; + + chown murmur:murmur "${cfg.murmurStore}/key.pem" + chown murmur:murmur "${cfg.murmurStore}/fullchain.pem" + ''; + }; + }; +} diff --git a/modules/by-name/ne/nextcloud/module.nix b/modules/by-name/ne/nextcloud/module.nix new file mode 100644 index 0000000..2e40970 --- /dev/null +++ b/modules/by-name/ne/nextcloud/module.nix @@ -0,0 +1,78 @@ +{ + config, + pkgs, + lib, + ... +}: let + cfg = config.vhack.nextcloud; +in { + options.vhack.nextcloud = { + enable = lib.mkEnableOption "a sophisticated nextcloud setup"; + package = lib.mkOption { + type = lib.types.package; + default = pkgs.nextcloud31; + description = "The nextcloud package to use"; + }; + hostname = lib.mkOption { + type = lib.types.str; + description = "The nextcloud hostname (fqdn)"; + }; + adminpassFile = lib.mkOption { + type = lib.types.path; + description = "The age encrypted admin password file"; + }; + }; + config = lib.mkIf cfg.enable { + vhack = { + nginx.enable = true; + postgresql.enable = true; + persist.directories = [ + "/var/lib/nextcloud" + ]; + }; + age.secrets = { + adminpassFile = { + file = cfg.adminpassFile; + mode = "0700"; + owner = "nextcloud"; + group = "nextcloud"; + }; + }; + + services = { + nextcloud = { + enable = true; + configureRedis = true; + config = { + adminuser = "admin"; + adminpassFile = config.age.secrets.adminpassFile.path; + dbname = "nextcloud"; + dbuser = "nextcloud"; + dbtype = "pgsql"; + }; + database.createLocally = true; + hostName = cfg.hostname; + https = true; + maxUploadSize = "5G"; + package = cfg.package; + settings = { + default_phone_region = "DE"; + }; + }; + nginx.virtualHosts.${cfg.hostname} = { + forceSSL = true; + enableACME = true; + }; + }; + users = { + users = { + "nextcloud".uid = config.vhack.constants.ids.uids.nextcloud; + "redis-nextcloud".uid = config.vhack.constants.ids.uids.redis-nextcloud; + }; + groups = { + "nextcloud".gid = config.vhack.constants.ids.gids.nextcloud; + "redis-nextcloud".gid = config.vhack.constants.ids.gids.redis-nextcloud; + }; + }; + }; +} diff --git a/modules/by-name/ng/nginx/module.nix b/modules/by-name/ng/nginx/module.nix index 39919c9..1cb4e46 100644 --- a/modules/by-name/ng/nginx/module.nix +++ b/modules/by-name/ng/nginx/module.nix @@ -6,7 +6,7 @@ mkRedirect = _: value: { forceSSL = true; enableACME = true; - locations."/".return = "301 ${value}"; + locations."/".return = "301 ${value}$request_uri"; }; redirects = builtins.mapAttrs mkRedirect cfg.redirects; @@ -66,15 +66,6 @@ in { services.nginx = { enable = true; virtualHosts = redirects; - - # FIXME(@bpeetz): Migrate to a host. <2024-12-25> - # { - # "gallery.s-schoeffel.de" = { - # forceSSL = true; - # enableACME = true; - # root = "/srv/gallery.s-schoeffel.de"; - # }; - # } }; }; } diff --git a/modules/by-name/ni/nix-sync/hosts.nix b/modules/by-name/ni/nix-sync/hosts.nix deleted file mode 100644 index 98dbbf1..0000000 --- a/modules/by-name/ni/nix-sync/hosts.nix +++ /dev/null @@ -1,48 +0,0 @@ -{...}: let - extraWkdSettings = { - locations."/.well-known/openpgpkey/hu/".extraConfig = '' - default_type application/octet-stream; - - # Came from: https://www.uriports.com/blog/setting-up-openpgp-web-key-directory/ - # No idea if it is actually necessary - # add_header Access-Control-Allow-Origin * always; - ''; - }; -in [ - { - domain = "vhack.eu"; - url = "https://codeberg.org/vhack.eu/website.git"; - } - { - domain = "b-peetz.de"; - url = "https://codeberg.org/bpeetz/b-peetz.de.git"; - } - - # Trinitrix - { - domain = "trinitrix.vhack.eu"; - url = "https://codeberg.org/trinitrix/website.git"; - } - - # WKD - { - domain = "openpgpkey.b-peetz.de"; - url = "https://codeberg.org/vhack.eu/gpg_wkd.git"; - extraSettings = extraWkdSettings; - } - { - domain = "openpgpkey.s-schoeffel.de"; - url = "https://codeberg.org/vhack.eu/gpg_wkd.git"; - extraSettings = extraWkdSettings; - } - { - domain = "openpgpkey.sils.li"; - url = "https://codeberg.org/vhack.eu/gpg_wkd.git"; - extraSettings = extraWkdSettings; - } - { - domain = "openpgpkey.vhack.eu"; - url = "https://codeberg.org/vhack.eu/gpg_wkd.git"; - extraSettings = extraWkdSettings; - } -] diff --git a/modules/by-name/ni/nix-sync/module.nix b/modules/by-name/ni/nix-sync/module.nix index de096b9..9ddd210 100644 --- a/modules/by-name/ni/nix-sync/module.nix +++ b/modules/by-name/ni/nix-sync/module.nix @@ -1,43 +1,44 @@ { config, lib, + modulesPath, + nixLib, ... }: let cfg = config.vhack.nix-sync; mkNixSyncRepository = { domain, - root ? "", - url, - extraSettings ? {}, + repositoryUrl, + extraSettings, }: { name = "${domain}"; value = { - path = "/etc/nginx/websites/${domain}/${root}"; - uri = "${url}"; + path = "/etc/nginx/websites/${domain}"; + uri = "${repositoryUrl}"; inherit extraSettings; }; }; - nixSyncRepositories = builtins.listToAttrs (builtins.map mkNixSyncRepository domains); + nixSyncRepositories = builtins.listToAttrs (builtins.map mkNixSyncRepository cfg.domains); mkVirtHost = { domain, - root ? "", - url, - extraSettings ? {}, + repositoryUrl, + extraSettings, }: { name = "${domain}"; value = - lib.recursiveUpdate { + # FIXME(@bpeetz): We cannot use something like `lib.recursiveUpdate` because the + # `extraSettings` are instantiated from the “real” nginx type. As such the + # `extaSettings` would override our values here. Therefore, the direct merge. <2025-02-07> + extraSettings + // { forceSSL = true; enableACME = true; - root = "/etc/nginx/websites/${domain}/${root}"; - } - extraSettings; + root = "/etc/nginx/websites/${domain}"; + }; }; - virtHosts = builtins.listToAttrs (builtins.map mkVirtHost domains); - - domains = import ./hosts.nix {}; + virtHosts = builtins.listToAttrs (builtins.map mkVirtHost cfg.domains); in { imports = [ ./internal_module.nix @@ -47,6 +48,38 @@ in { enable = lib.mkEnableOption '' a website git ops solution. ''; + + domains = lib.mkOption { + type = lib.types.listOf (lib.types.submodule { + options = { + domain = lib.mkOption { + type = lib.types.str; + example = "b-peetz.de"; + description = '' + The fully qualified domain to use as base of this website. + ''; + }; + repositoryUrl = lib.mkOption { + type = lib.types.str; + example = "b-peetz.de"; + description = '' + The url used for the source git repository, which is deployed at this domain. + ''; + }; + extraSettings = lib.mkOption { + type = + lib.types.submodule (import (modulesPath + "/services/web-servers/nginx/vhost-options.nix") {inherit config lib;}); + example = { + locations."/.well-known/openpgpkey/".extraConfig = "default_type application/octet-stream"; + }; + default = {}; + description = '' + Extra configuration to add to the nginx virtual host. + ''; + }; + }; + }); + }; }; config = lib.mkIf cfg.enable { @@ -66,5 +99,10 @@ in { vhack.nginx.enable = true; services.nginx.virtualHosts = virtHosts; + + users = { + users.nix-sync.uid = config.vhack.constants.ids.uids.nix-sync; + groups.nix-sync.gid = config.vhack.constants.ids.gids.nix-sync; + }; }; } diff --git a/modules/by-name/ni/nixconfig/module.nix b/modules/by-name/ni/nixconfig/module.nix new file mode 100644 index 0000000..a5bf950 --- /dev/null +++ b/modules/by-name/ni/nixconfig/module.nix @@ -0,0 +1,28 @@ +{ + config, + lib, + ... +}: let + cfg = config.vhack.nixconfig; +in { + options.vhack.nixconfig = { + enable = lib.mkEnableOption "sophisticated nix settings"; + }; + config = lib.mkIf cfg.enable { + nix = { + # gc = { + # automatic = true; + # dates = "daily"; + # options = "--delete-older-than 3"; + # }; + settings = { + auto-optimise-store = true; + experimental-features = ["nix-command" "flakes"]; + trusted-users = [ + "root" + "@wheel" + ]; + }; + }; + }; +} diff --git a/modules/by-name/pe/peertube/module.nix b/modules/by-name/pe/peertube/module.nix index 29d1d07..e65e0b5 100644 --- a/modules/by-name/pe/peertube/module.nix +++ b/modules/by-name/pe/peertube/module.nix @@ -1,7 +1,6 @@ { config, lib, - pkgs, ... }: let cfg = config.vhack.peertube; @@ -10,6 +9,14 @@ in { enable = lib.mkEnableOption '' the peertube video platform. ''; + peertubeGeneral = lib.mkOption { + type = lib.types.path; + description = "The age encrypted general secret file passed to agenix"; + }; + smtpPasswordFile = lib.mkOption { + type = lib.types.path; + description = "The age encrypted smtp password file passed to agenix"; + }; }; config = lib.mkIf cfg.enable { @@ -22,7 +29,7 @@ in { listenWeb = 443; smtp = { - createLocally = true; + createLocally = false; passwordFile = "${config.age.secrets.peertubeSmtp.path}"; }; database = { @@ -66,10 +73,8 @@ in { smtp = let emailAddress = "peertube@vhack.eu"; in { - sendmail = "${pkgs.postfix}/bin/sendmail"; - - transport = "sendmail"; - hostname = "server1.vhack.eu"; + transport = "smtp"; + hostname = "mail.foss-syndicate.org"; port = 587; username = emailAddress; tls = true; @@ -88,20 +93,20 @@ in { age.secrets = { peertubeGeneral = { - file = ./secrets/general.age; + file = cfg.peertubeGeneral; mode = "700"; owner = "peertube"; group = "peertube"; }; peertubeSmtp = { - file = ./secrets/smtp.age; + file = cfg.smtpPasswordFile; mode = "700"; owner = "peertube"; group = "peertube"; }; }; - environment.persistence."/srv".directories = [ + vhack.persist.directories = [ { directory = "/var/lib/peertube"; user = "peertube"; @@ -109,5 +114,11 @@ in { mode = "0700"; } ]; + users = { + users.peertube.uid = config.vhack.constants.ids.uids.peertube; + groups.peertube.gid = config.vhack.constants.ids.gids.peertube; + users.redis-peertube.uid = config.vhack.constants.ids.uids.redis-peertube; + groups.redis-peertube.gid = config.vhack.constants.ids.gids.redis-peertube; + }; }; } diff --git a/modules/by-name/pe/peertube/secrets/general.age b/modules/by-name/pe/peertube/secrets/general.age deleted file mode 100644 index 854ab1a..0000000 --- a/modules/by-name/pe/peertube/secrets/general.age +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN AGE ENCRYPTED FILE----- -YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlNjR4TDVUZmY2Y0hYT2hk -YmtPcFIxSXplNWF4M0V1Kzh2b2VoSTFCK0dzCmpwT2tDa3FpR082V2pyelBoS05o -RmlWRVdNdVhZbkRVUEVnaDlPdlN1bDAKLT4gWDI1NTE5IFlvaTFPc2JHcWczbEJy -eVZDS2NaUzBvbnpadk5ySVFxRTlNVXhrd2N0a3MKanJ0NEZWaTg3dE5Cbm9uNHNS -ZCs2dmU4RkFZOHNyNlJKa0cyd2VqSlFPQQotPiBzc2gtZWQyNTUxOSBPRDhUNGcg -NXhFSHdWUk1sbEUyb3FTdGpIaHlyTUJlMnlzNXBEY2lzTXpuM09WVDBrOApmM05W -d1VBSGlhMmlDYlhZS1hSdlJBUVkrVWs0bTJseS9BUmZGY1l5K0NBCi0+IEQkNi1l -LWdyZWFzZSAhIUlaOnNsZCAsUVRVKiBfRig2KGg+NSA6CmI0Q0N0cmlFbnNGSFZQ -WThEV0RHS0V2NTVaZnIyK2tUQXZTOHdsRkhyRlExdCtOeHRML2hFNDNxd08xQjlG -V3oKMThoQnF4Y3FDU3hMZjhwRUNvVWRRR3I4c1k5QnhJS1dRR2dod0EKLS0tIEZT -dHhnVXdHV3QzYThXWFJQL2szeTZ4SWM4czZYQWxJOFFIVjBZSnJ0K00KH8WdXv68 -rjAqo5RoWu91aVg5Bl2HKuiFbaGcnlkiMPZ9wGfpq4mpCc/yc4NTa6HhkaI5tA61 -PjKurnkiLXywcdyUTPuaykk+wANynLucbwfq/Mv3aLcG01soh+dFNKZV/g== ------END AGE ENCRYPTED FILE----- diff --git a/modules/by-name/pe/peertube/secrets/smtp.age b/modules/by-name/pe/peertube/secrets/smtp.age deleted file mode 100644 index 1979ea7..0000000 --- a/modules/by-name/pe/peertube/secrets/smtp.age +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN AGE ENCRYPTED FILE----- -YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtU05NMDN0Q2MrVGEraHpH -Tkwyd0NuVEQwcjljd0NUNUpsSER0V0RldWxNCjlnRHZWNmprVDYxQm90Q3pFVHR5 -enJyUTZhSVdUL1I1aC9Ya2NkMElQaFUKLT4gWDI1NTE5IHprWjRDZVlMK3Rmd1A5 -K0pZRVBIYldsOW0wQXp4SmJzM1pXdzAvZVpiWEUKd1cwR2ZNZTh6WXhQNGZBVmdN -VWpxZGxPZXJBT1dqUFd1aU4xaHAxckZLcwotPiBzc2gtZWQyNTUxOSBPRDhUNGcg -akdaS3I3VHplOENIZDg5TSt2SmRCSGpjaUZoUHVYTFJRR2wzc1RHYWNnVQpFN1Ew -MTZDNGNyKzB1aEdTMHpKaWlFLzE0blJpZ3RhOTZReTNucUp6SEdZCi0+IHloQSUq -LWdyZWFzZQo5VitXYjNxck5FbnkwYlBvUyt6R2ROVG9JOWtQNGJma1ZYd29oVlFx -blFzSytWNDA4d3lqWE9JTUVreCs2Wi92ClZCdFgwYmRmc1VsU0NhTVR4b2dtZkpK -ZTU2M24zVjd0UTRrelFXYnFEZwotLS0gT2ZlRGJsZWNPcEwxK2drdDhVSndDV3Fj -SENsN2piWWEzSFI2OW8xbk12cwrFU4dzHxb5M3miGDpWLh3XbwzsrqWlFWLLu0Ht -SDvqJGrwAPsnVn4YLSG42q1BodYfcQVvVwqRCVbkubEUDcecDTdaYDvjaS3tmDZW -u5Nabp1ujYuIewOEZ8w41napS0C553qq0mL5sYZH1C23ViW81va4X1XOJTCnmbz6 -lbh+lK8ZbZz3cer49nR8OHTtpjA9hrf4Pf/W2nMR+0exy4zDYw== ------END AGE ENCRYPTED FILE----- diff --git a/modules/by-name/re/redlib/module.nix b/modules/by-name/re/redlib/module.nix index 2b20c66..eb5edba 100644 --- a/modules/by-name/re/redlib/module.nix +++ b/modules/by-name/re/redlib/module.nix @@ -31,14 +31,11 @@ in { enableACME = true; forceSSL = true; }; + }; - # TODO: Remove this at a certain point. <2024-12-19> - virtualHosts."libreddit.vhack.eu" = { - locations."/".return = "301 https://${domain}"; - - forceSSL = true; - enableACME = true; - }; + # TODO(@bpeetz): Remove this at some point. <2025-02-04> + vhack.nginx.redirects = { + "libreddit.vhack.eu" = "${domain}"; }; }; } diff --git a/modules/by-name/st/stalwart-mail/module.nix b/modules/by-name/st/stalwart-mail/module.nix new file mode 100644 index 0000000..1ad76c7 --- /dev/null +++ b/modules/by-name/st/stalwart-mail/module.nix @@ -0,0 +1,402 @@ +{ + lib, + config, + pkgs, + vhackPackages, + ... +}: let + cfg = config.vhack.stalwart-mail; + topCfg = config.services.stalwart-mail; + + configFormat = pkgs.formats.toml {}; + configFile = configFormat.generate "stalwart-mail.toml" topCfg.settings; +in { + imports = [ + ./settings.nix + ]; + + options.vhack.stalwart-mail = { + enable = lib.mkEnableOption "starwart-mail"; + + package = lib.mkPackageOption vhackPackages "stalwart-mail-free" {}; + + admin = lib.mkOption { + description = '' + Email address to advertise as administrator. This is the address, where dkim, spv + etc. refusal reports are sent to. + + The format should be: `mailto:<name>@<domain>` + ''; + type = lib.types.str; + example = "mailto:dmarc+rua@example.com"; + default = ""; + }; + + fqdn = lib.mkOption { + type = lib.types.str; + example = "mail.foss-syndicate.org"; + description = '' + The fully qualified domain name for this mail server. + ''; + }; + + principals = lib.mkOption { + default = []; + type = lib.types.listOf (lib.types.submodule { + options = { + name = lib.mkOption { + type = lib.types.str; + description = "Specifies the username of the account"; + }; + + class = lib.mkOption { + type = lib.types.enum ["individual" "admin"]; + description = "Specifies the account type"; + }; + + description = lib.mkOption { + type = lib.types.str; + description = "Provides a description or full name for the user"; + default = ""; + }; + + secret = lib.mkOption { + type = lib.types.str; + description = '' + Sets the password for the user account. + Passwords can be stored hashed or in plain text (not recommended). + See <https://stalw.art/docs/auth/authentication/password/> for a description + of password encoding. + ''; + }; + email = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = '' + A list of email addresses associated with the user. + The first address in the list is considered the primary address. + ''; + }; + }; + }); + }; + + dataDirectory = lib.mkOption { + description = '' + The directory in which to store all storage things. + ''; + default = "/var/lib/stalwart-mail"; + type = lib.types.path; + readOnly = true; + }; + + openFirewall = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Whether to open TCP firewall ports, which are specified in + {option}`services.stalwart-mail.settings.listener` on all interfaces. + ''; + }; + + security = lib.mkOption { + type = lib.types.nullOr (lib.types.submodule { + options = { + verificationMode = lib.mkOption { + type = lib.types.enum ["relaxed" "strict"]; + description = '' + Whether to allow invalid signatures/checks or not. + ''; + default = "relaxed"; + }; + + dkimKeys = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule { + options = { + dkimPublicKey = lib.mkOption { + type = lib.types.str; + description = '' + The base 64 encoded representation of the public dkim key. + ''; + }; + dkimPrivateKeyPath = lib.mkOption { + type = lib.types.path; + description = '' + The path to the dkim private key agenix file. + Generate it via the `./gen_key` script: + ''; + }; + keyAlgorithm = lib.mkOption { + type = lib.types.enum ["ed25519-sha256" "rsa-sha-256" "rsa-sha-1"]; + description = "The algorithm of the used key"; + }; + }; + }); + description = '' + Which key to use for which domain. The attr keys are the domains + ''; + default = {}; + }; + }; + }); + description = '' + Security options. This should only be set to `null` when testing. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = cfg.admin != ""; + message = "You need to specify an admin address."; + } + ]; + + vhack.nginx.enable = true; + services = { + stalwart-mail = { + # NOTE(@bpeetz): We do not use the NixOS service, as it comes with too much + # bothersome default configuration and not really any useful configuration. + # However, this decision could obviously be reversed in the future. <2025-02-08> + enable = false; + inherit (cfg) package; + # dataDir = cfg.dataDirectory; + }; + + # FIXME(@bpeetz): This is currently needed for a successful acme http-01 challenge. + # We could also use the DNS challenge. <2025-03-01> + nginx.virtualHosts."${cfg.fqdn}" = { + enableACME = false; + extraConfig = + # This is copied directly from the nixos nginx module. + # Rule for legitimate ACME Challenge requests (like /.well-known/acme-challenge/xxxxxxxxx) + # We use ^~ here, so that we don't check any regexes (which could + # otherwise easily override this intended match accidentally). + '' + location ^~ /.well-known/acme-challenge/ { + root ${config.security.acme.certs.${cfg.fqdn}.webroot}; + auth_basic off; + auth_request off; + } + ''; + }; + + redis = { + servers = { + "stalwart-mail" = { + enable = true; + + user = "stalwart-mail"; + + # Disable TCP listening. (We have a UNIX socket) + port = 0; + bind = null; + + settings = { + protected-mode = true; + enable-protected-configs = false; + enable-debug-command = false; + enable-module-command = false; + + supervised = "systemd"; + stop-writes-on-bgsave-error = true; + sanitize-dump-payload = "clients"; + }; + }; + }; + }; + }; + security.acme.certs = { + "${cfg.fqdn}" = { + domain = cfg.fqdn; + group = "stalwart-mail"; + }; + }; + + age.secrets = let + keys = + lib.mapAttrs' ( + keyDomain: keyConfig: + lib.nameValuePair "stalwartMail${keyDomain}" + { + file = keyConfig.dkimPrivateKeyPath; + mode = "600"; + owner = "stalwart-mail"; + group = "stalwart-mail"; + } + ) + cfg.security.dkimKeys; + in + lib.mkIf (cfg.security != null) keys; + + vhack.persist.directories = [ + { + directory = "${cfg.dataDirectory}"; + user = "stalwart-mail"; + group = "stalwart-mail"; + mode = "0700"; + } + { + directory = "${config.services.redis.servers."stalwart-mail".settings.dir}"; + user = "stalwart-mail"; + group = "redis"; + mode = "0770"; + } + ]; + + # This service stores a potentially large amount of data. + # Running it as a dynamic user would force chown to be run every time the + # service is restarted on a potentially large number of files. + # That would cause unnecessary and unwanted delays. + users = { + groups.stalwart-mail = { + gid = config.vhack.constants.ids.gids.stalwart-mail; + }; + users.stalwart-mail = { + isSystemUser = true; + group = "stalwart-mail"; + uid = config.vhack.constants.ids.uids.stalwart-mail; + }; + groups.redis-stalwart-mail = { + gid = config.vhack.constants.ids.gids.redis-stalwart-mail; + }; + users.redis-stalwart-mail = { + group = "redis-stalwart-mail"; + uid = config.vhack.constants.ids.uids.redis-stalwart-mail; + }; + }; + + systemd.tmpfiles.rules = [ + "d '${cfg.dataDirectory}' - stalwart-mail stalwart-mail - -" + ]; + + systemd = { + services.stalwart-mail = { + wantedBy = ["multi-user.target"]; + requires = + [ + "redis-stalwart-mail.service" + "network-online.target" + ] + ++ (lib.optional (cfg.security != null) "acme-${cfg.fqdn}.service"); + after = [ + "local-fs.target" + "network.target" + "network-online.target" + "redis-stalwart-mail.service" + "acme-${cfg.fqdn}.service" + ]; + conflicts = [ + "postfix.service" + "sendmail.service" + "exim4.service" + ]; + description = "Stalwart Mail Server"; + + environment = { + SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt"; + NIX_SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt"; + }; + + preStart = let + esa = lib.strings.escapeShellArg; + mkTmpFile = path: "[ -d ${esa path} ] || mkdir --parents ${esa path}"; + + # Create the directories for stalwart + storageDirectories = lib.lists.filter (v: v != null) (lib.attrsets.mapAttrsToList (_: {path ? null, ...}: + if (path != null) + then mkTmpFile path + else null) + topCfg.settings.store); + in + '' + # Stalwart actually wants to store _data_ (e.g., blocked ips) in it's own config file. + # Thus we need to make it writable. + cat ${esa configFile} >$CACHE_DIRECTORY/mutable_config_file.toml + '' + + (builtins.concatStringsSep "\n" storageDirectories); + + serviceConfig = { + ExecStart = pkgs.writers.writeDash "start-stalwart-mail" '' + ${lib.getExe cfg.package} --config="$CACHE_DIRECTORY/mutable_config_file.toml" + ''; + + Restart = "on-failure"; + RestartSec = 5; + + KillMode = "process"; + KillSignal = "SIGINT"; + + Type = "simple"; + LimitNOFILE = 65536; + + StandardOutput = "journal"; + StandardError = "journal"; + + ReadWritePaths = [ + cfg.dataDirectory + ]; + CacheDirectory = "stalwart-mail"; + StateDirectory = "stalwart-mail"; + + User = "stalwart-mail"; + Group = "stalwart-mail"; + + SyslogIdentifier = "stalwart-mail"; + + # Bind standard privileged ports + AmbientCapabilities = ["CAP_NET_BIND_SERVICE"]; + CapabilityBoundingSet = ["CAP_NET_BIND_SERVICE"]; + + # Hardening + DeviceAllow = [""]; + LockPersonality = true; + MemoryDenyWriteExecute = true; + PrivateDevices = true; + PrivateUsers = false; # incompatible with CAP_NET_BIND_SERVICE + ProcSubset = "pid"; + PrivateTmp = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_UNIX" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "~@privileged" + ]; + UMask = "0077"; + }; + }; + }; + + # Make admin commands available in the shell + environment.systemPackages = [cfg.package]; + + networking.firewall = let + parsePorts = listeners: let + parseAddresses = listeners: lib.flatten (lib.mapAttrsToList (name: value: value.bind) listeners); + splitAddress = addr: lib.splitString ":" addr; + extractPort = addr: lib.toInt (builtins.foldl' (a: b: b) "" (splitAddress addr)); + in + builtins.map extractPort (parseAddresses listeners); + in + lib.mkIf (cfg.openFirewall && (builtins.hasAttr "listener" topCfg.settings.server)) + { + allowedTCPPorts = parsePorts topCfg.settings.server.listener; + }; + }; +} diff --git a/modules/by-name/st/stalwart-mail/settings.nix b/modules/by-name/st/stalwart-mail/settings.nix new file mode 100644 index 0000000..7032ae0 --- /dev/null +++ b/modules/by-name/st/stalwart-mail/settings.nix @@ -0,0 +1,532 @@ +{ + config, + lib, + pkgs, + ... +}: let + cfg = config.vhack.stalwart-mail; + + signaturesByDomain = + (builtins.map ({name, ...}: { + "if" = "sender_domain = '${name}'"; + "then" = "'${name}'"; + }) + (lib.attrsToList cfg.security.dkimKeys)) + ++ [{"else" = false;}]; + + maybeVerificationMode = + if cfg.security != null + then cfg.security.verificationMode + else "disable"; +in { + config.services.stalwart-mail.settings = lib.mkIf cfg.enable { + # https://www.rfc-editor.org/rfc/rfc6376.html#section-3.3 + signature = let + signatures = + lib.mapAttrs (keyDomain: keyConfig: { + private-key = "%{file:${config.age.secrets."stalwartMail${keyDomain}".path}}%"; + + domain = keyDomain; + + selector = "mail"; + headers = ["From" "To" "Cc" "Date" "Subject" "Message-ID" "Organization" "MIME-Version" "Content-Type" "In-Reply-To" "References" "List-Id" "User-Agent" "Thread-Topic" "Thread-Index"]; + algorithm = keyConfig.keyAlgorithm; + + # How do we canonicalize the headers/body? + # https://www.rfc-editor.org/rfc/rfc6376.html#section-3.4 + canonicalization = "simple/simple"; + + expire = "50d"; + report = true; + }) + cfg.security.dkimKeys; + in + lib.mkIf (cfg.security != null) signatures; + + auth = let + # NOTE(@bpeetz): We disable all the checks if the `listener` is submissions, because the + # user's email client will obviously not have the right IP address to pass SPF or + # IPREV. It will also not be able to sign the message with DKIM (as we has to key). <2025-02-25> + ifNotSmpt = valueTrue: valueFalse: [ + { + "if" = "listener != 'submissions'"; + "then" = valueTrue; + } + {"else" = valueFalse;} + ]; + in { + iprev = { + verify = ifNotSmpt maybeVerificationMode "disable"; + }; + spf = { + verify = { + ehlo = ifNotSmpt maybeVerificationMode "disable"; + + mail-from = ifNotSmpt maybeVerificationMode "disable"; + }; + }; + dmarc = { + verify = ifNotSmpt maybeVerificationMode "disable"; + }; + arc = { + seal = lib.mkIf (cfg.security != null) signaturesByDomain; + verify = ifNotSmpt maybeVerificationMode "disable"; + }; + dkim = { + verify = ifNotSmpt maybeVerificationMode "disable"; + + # Ignore insecure dkim signed messages (i.e., messages containing both + # signed and appended not-signed content.) + strict = true; + + sign = + lib.mkIf (cfg.security != null) signaturesByDomain; + }; + }; + report = { + domain = "${cfg.fqdn}"; + submitter = "'${cfg.fqdn}'"; + analysis = { + addresses = ["dmarc@*" "abuse@*"]; + forward = true; + store = "30d"; + }; + tls.aggregate = { + from-name = "'TLS Report'"; + from-address = "'noreply-tls@${cfg.fqdn}'"; + org-name = "'Foss Syndicate Mail Handling'"; + contact-info = "'${cfg.admin}'"; + send = "daily"; + max-size = 26214400; # 25 MiB + sign = lib.mkIf (cfg.security != null) "'${cfg.fqdn}'"; + }; + dmarc = { + aggregate = { + from-name = "'DMARC Report'"; + from-address = "'noreply-dmarc@${cfg.fqdn}'"; + org-name = "'Foss Syndicate Mail Handling'"; + contact-info = "'${cfg.admin}'"; + send = "weekly"; + max-size = 26214400; # 25MiB + sign = lib.mkIf (cfg.security != null) "'${cfg.fqdn}'"; + }; + from-name = "'Report Subsystem'"; + from-address = "'noreply-dmarc@${cfg.fqdn}'"; + subject = "'DMARC Authentication Failure Report'"; + send = "1/1d"; + sign = lib.mkIf (cfg.security != null) signaturesByDomain; + }; + spf = { + from-name = "'Report Subsystem'"; + from-address = "'noreply-spf@${cfg.fqdn}'"; + subject = "'SPF Authentication Failure Report'"; + send = "1/1d"; + sign = lib.mkIf (cfg.security != null) signaturesByDomain; + }; + dkim = { + from-name = "'Report Subsystem'"; + from-address = "'noreply-dkim@${cfg.fqdn}'"; + subject = "'DKIM Authentication Failure Report'"; + send = "1/1d"; + sign = lib.mkIf (cfg.security != null) signaturesByDomain; + }; + dsn = { + from-name = "'Mail Delivery Subsystem'"; + from-address = "'MAILER-DAEMON@${cfg.fqdn}'"; + sign = lib.mkIf (cfg.security != null) signaturesByDomain; + }; + }; + queue = { + schedule = { + retry = "[2m, 5m, 10m, 15m, 30m, 1h, 2h]"; + notify = "[2h, 7h, 1d, 3d]"; + expire = "5d"; + }; + outbound = { + tls = { + starttls = + if maybeVerificationMode == "strict" + then "require" + else "optional"; + allow-invalid-certs = false; + ip-strategy = "ipv6_then_ipv4"; + mta-sts = + if maybeVerificationMode == "strict" + then "require" + else "optional"; + }; + }; + }; + resolver = { + type = "system"; + preserve-intermediates = true; + concurrency = 2; + timeout = "5s"; + attempts = 2; + try-tcp-on-error = true; + public-suffix = [ + "file://${pkgs.publicsuffix-list}/share/publicsuffix/public_suffix_list.dat" + ]; + }; + + spam-filter = { + enable = true; + header = { + status = { + enable = true; + name = "X-Spam-Status"; + }; + result = { + enable = true; + name = "X-Spam-Result"; + }; + }; + bayes = { + enable = true; + + # Learn from users putting mail into JUNK or taking mail out of it. + account = { + enable = true; + }; + }; + + # Fetch the newest spam-filter rules not from github, but from the nix + # package. + resource = "file://${cfg.package.passthru.spamfilter}/spam-filter.toml"; + auto-update = false; + }; + + webadmin = { + # Fetch the newest webadmin bundle not from github, but from the nix + # package. + resource = "file://${cfg.package.passthru.webadmin}/webadmin.zip"; + auto-update = false; + path = "/var/cache/stalwart-mail"; + }; + + session = { + milter = { + # TODO: Add this <2025-02-07> + # "clamav" = { + # enable = true; + # hostname = "127.0.0.1"; + # port = 15112; + # tls = false; + # allow-invalid-certs = false; + # }; + }; + ehlo = { + require = true; + }; + rcpt = { + directory = "'in-memory'"; + catch-all = true; + subaddressing = true; + }; + data = { + spam-filter = true; + add-headers = { + received = true; + received-spf = true; + auth-results = true; + message-id = true; + date = true; + return-path = true; + delivered-to = true; + }; + auth = { + mechanisms = ["LOGIN" "PLAIN"]; + directory = "'in-memory'"; + require = true; + must-match-sender = true; + errors = { + total = 3; + wait = "5s"; + }; + }; + }; + extensions = { + pipelining = true; + chunking = true; + requiretls = true; + no-soliciting = ""; + dsn = [ + { + "if" = "!is_empty(authenticated_as)"; + "then" = true; + } + {"else" = false;} + ]; + future-release = [ + { + "if" = "!is_empty(authenticated_as)"; + "then" = "7d"; + } + {"else" = false;} + ]; + deliver-by = [ + { + "if" = "!is_empty(authenticated_as)"; + "then" = "15d"; + } + {"else" = false;} + ]; + mt-priority = [ + { + "if" = "!is_empty(authenticated_as)"; + "then" = "mixer"; + } + {"else" = false;} + ]; + vrfy = [ + { + "if" = "!is_empty(authenticated_as)"; + "then" = true; + } + {"else" = false;} + ]; + expn = [ + { + "if" = "!is_empty(authenticated_as)"; + "then" = true; + } + {"else" = false;} + ]; + }; + }; + + jmap = { + account = { + purge.frequency = "0 0 *"; + }; + protocol = { + changes.max-history = "14d"; + }; + email = { + # NOTE(@bpeetz): We probably want to enable the auto-deletion of emails in + # the "Junk" and "Deleted" items mail folders, but this should be + # communicated to the users. <2025-02-07> + auto-expunge = false; + }; + mailbox = { + max-depth = 50; + max-name-length = 255; + }; + folders = let + mkFolder = name: { + inherit name; + create = true; + subscribe = true; + }; + in { + inbox = mkFolder "INBOX"; + drafts = mkFolder "DRAFTS"; + sent = mkFolder "SENT"; + trash = mkFolder "TRASH"; + archive = mkFolder "ARCHIVE"; + junk = mkFolder "JUNK"; + shared = {name = "SHARED";}; + }; + }; + imap = { + auth = { + # Allow password login over non tls connection + allow-plain-text = false; + }; + }; + + server = { + hostname = cfg.fqdn; + + listener = { + # TODO(@bpeetz): Add this <2025-02-08> + # # HTTP (used for jmap) + # "http" = { + # bind = ["[::]:8080"]; + # protocol = "http"; + # tls.implicit = true; + # }; + + # IMAP + "imap" = { + bind = ["[::]:993"]; + protocol = "imap"; + tls.implicit = true; + }; + + # SMTP + "submissions" = { + bind = ["[::]:465"]; + protocol = "smtp"; + tls.implicit = true; + }; + "input" = { + bind = ["[::]:25"]; + protocol = "smtp"; + tls = { + enable = true; + # Require an explicit `STARTTLS` + implicit = false; + }; + }; + + # # POP3 (should be disabled, unless there is a real reason to use it) + # "pop3" = { + # bind = ["[::]:995"]; + # protocol = "pop3"; + # tls.implicit = true; + # }; + + # # LMTP + # "lmtp" = { + # bind = ["[::]:24"]; + # protocol = "lmtp"; + # }; + + # ManageSieve + "managesieve" = { + bind = ["[::]:4190"]; + protocol = "managesieve"; + tls.implicit = true; + }; + }; + + tls = { + enable = true; + + # Expect the client connection to be encrypted from the start (i.e., + # without STARTTLS) + implicit = true; + + certificate = "default"; + }; + + # TODO(@bpeetz): Configure that <2025-02-07> + # http = { + # url = ""; + # allowed-endpoint = ["404"]; + # }; + + auto-ban = { + # Ban if the same IP fails to login 10 times in a day + rate = "10/1d"; + + # Ban the login for an user account, if different IP-Addresses tried and + # failed to login 100 times in single day + auth.rate = "100/1d"; + + abuse.rate = "35/1d"; + + loiter.rate = "150/1d"; + + scan.rate = "150/1d"; + }; + + cache = let + MiB = 1024 * 1024; + in { + access-token.size = 10 * MiB; + http-auth.size = 1 * MiB; + permission.size = 5 * MiB; + account.size = 10 * MiB; + mailbox.size = 10 * MiB; + thread.size = 10 * MiB; + bayes.size = 10 * MiB; + dns = { + txt.size = 5 * MiB; + mx.size = 5 * MiB; + ptr.size = 1 * MiB; + ipv4.size = 5 * MiB; + ipv6.size = 5 * MiB; + tlsa.size = 1 * MiB; + mta-sts.size = 1 * MiB; + rbl.size = 5 * MiB; + }; + }; + }; + + tracer = { + # NOTE(@bpeetz): + # We are using the console logger, because that has nice color output. + # Simply using the console should be fine, as systemd pipes that to the journal + # either way. <2025-02-08> + console = { + enable = true; + ansi = true; + level = "info"; + type = "console"; + }; + }; + + store = { + "rocksdb-data" = { + type = "rocksdb"; + path = "${cfg.dataDirectory}/storage/data"; + compression = "lz4"; + + # Perform “maintenance” every day at 3 am local time. + purge.frequency = "0 3 *"; + }; + "rocksdb-full-text-search" = { + type = "rocksdb"; + path = "${cfg.dataDirectory}/storage/full-text-search"; + compression = "lz4"; + + # Perform “maintenance” every day at 2 am local time. + purge.frequency = "0 2 *"; + }; + "file-system" = { + type = "fs"; + path = "${cfg.dataDirectory}/storage/blobs"; + depth = 2; + compression = "lz4"; + + # Perform “maintenance” every day at 5:30 am local time. + purge.frequency = "30 5 *"; + }; + "redis" = { + type = "redis"; + redis-type = "single"; + urls = "unix://${config.services.redis.servers."stalwart-mail".unixSocket}"; + timeout = "10s"; + + # Perform “maintenance” every day at 2:30 am local time. + purge.frequency = "30 2 *"; + }; + }; + storage = { + # PostgreSQL is an option, but this is recommended for single node + # configurations. + data = "rocksdb-data"; + + # We could also re-use the data storage backend for that. + blob = "file-system"; + + full-text.default-language = "en"; + fts = "rocksdb-full-text-search"; + + directory = "in-memory"; + + lookup = "redis"; + + # NOTE(@bpeetz): This will encrypt all emails with the users pgp key (if it + # can be determined.) This is a wonderful feature, but quite tiresome, if + # the user intends to read their email without a their pgp key present (for + # example via their smartphone.) <2025-02-07> + encryption.enable = false; + }; + + directory."in-memory" = { + type = "memory"; + inherit (cfg) principals; + }; + + certificate = { + "default" = { + cert = "%{file:${config.security.acme.certs.${cfg.fqdn}.directory}/fullchain.pem}%"; + private-key = "%{file:${config.security.acme.certs.${cfg.fqdn}.directory}/key.pem}%"; + default = true; + }; + }; + }; +} diff --git a/modules/by-name/sy/system-info/module.nix b/modules/by-name/sy/system-info/module.nix new file mode 100644 index 0000000..f04eb49 --- /dev/null +++ b/modules/by-name/sy/system-info/module.nix @@ -0,0 +1,70 @@ +{ + lib, + config, + pkgs, + ... +}: let + mkVirtualHostDisplay = name: value: let + aliases = + if value.serverAliases != [] + then + ": " + + builtins.concatStringsSep " " value.serverAliases + else ""; + in '' + ${name}${aliases} + ''; + vHosts = builtins.concatStringsSep "" (builtins.attrValues (builtins.mapAttrs mkVirtualHostDisplay config.services.nginx.virtualHosts)); + + mkOpenPortDisplay = mode: port: let + checkEnabled = service: name: + if config.vhack.${service}.enable + then name + else "<port is '${name}' but service 'vhack.${service}' is not enabled.>"; + mappings = { + "22" = checkEnabled "openssh" "ssh"; + "80" = checkEnabled "nginx" "http"; + "443" = checkEnabled "nginx" "https"; + + "53" = checkEnabled "dns" "dns"; + + "24" = checkEnabled "mail" "mail-lmtp"; + "465" = checkEnabled "mail" "mail-smtp-tls"; + "25" = checkEnabled "mail" "mail-smtp"; + "993" = checkEnabled "mail" "mail-imap-tls"; + "995" = checkEnabled "mail" "mail-pop3-tls"; + + # TODO(@bpeetz): Check which service opens these ports: <2025-01-28> + "64738" = "???"; + }; + in '' + ${mode} ${builtins.toString port}: ${mappings.${builtins.toString port}} + ''; + + # TODO(@bpeetz): This should probably also include the allowed TCP/UDP port ranges. <2025-01-28> + openTCPPorts = builtins.concatStringsSep "" (builtins.map (mkOpenPortDisplay "TCP") config.networking.firewall.allowedTCPPorts); + openUDPPorts = builtins.concatStringsSep "" (builtins.map (mkOpenPortDisplay "UDP") config.networking.firewall.allowedUDPPorts); + + markdown = pkgs.writeText "${config.networking.hostName}-system-info.md" '' + ## Virtual Hosts + ${vHosts} + ## Open ports + ${openTCPPorts} + ${openUDPPorts} + ''; +in { + options.vhack.system-info = { + markdown = lib.mkOption { + type = lib.types.package; + description = '' + A derivation, that builds a markdown file, showing relevant system + information for this host. + ''; + readOnly = true; + }; + }; + + config.vhack.system-info = { + inherit markdown; + }; +} diff --git a/modules/default.nix b/modules/default.nix index 61d259d..0618f90 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -1,21 +1,10 @@ {nixLib, ...}: let - files = - builtins.attrValues - (nixLib.mkByName { + files = builtins.attrValues ( + nixLib.mkByName { baseDirectory = ./by-name; fileName = "module.nix"; - - # We only want the base paths. - finalizeFunction = name: value: value; - - # TODO: Re-activate, when/if most modules have tests. <2024-11-23> - # coImportsNameFunction = { - # shard, - # name, - # }: - # ../tests/by-name + "/${shard}" + "/${name}" + "/test.nix"; - # coImportsWarnMessageObject = "test"; - }); + } + ); in { imports = files; } diff --git a/pkgs/by-name/ba/back/.envrc b/pkgs/by-name/ba/back/.envrc index e86f040..66c2f00 100644 --- a/pkgs/by-name/ba/back/.envrc +++ b/pkgs/by-name/ba/back/.envrc @@ -17,6 +17,5 @@ watch_file flake.nix PATH_add ./scripts if on_git_branch; then - echo && git status --short --branch && - echo && git fetch --verbose + echo && git status --short --branch fi diff --git a/pkgs/by-name/ba/back/Cargo.lock b/pkgs/by-name/ba/back/Cargo.lock index a200c3c..228a22f 100644 --- a/pkgs/by-name/ba/back/Cargo.lock +++ b/pkgs/by-name/ba/back/Cargo.lock @@ -34,7 +34,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom", "once_cell", "version_check", "zerocopy", @@ -111,11 +110,12 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", + "once_cell", "windows-sys 0.59.0", ] @@ -132,43 +132,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "async-trait" -version = "0.1.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] name = "atom_syndication" -version = "0.12.6" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec03a6e158ee0f38bfba811976ae909bc2505a4a2f4049c7e8df47df3497b119" +checksum = "d2f68d23e2cb4fd958c705b91a6b4c80ceeaf27a9e11651272a8389d5ce1a4a3" dependencies = [ "chrono", "derive_builder", @@ -178,19 +145,10 @@ dependencies = [ ] [[package]] -name = "atomic" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" - -[[package]] -name = "atomic" -version = "0.6.0" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" -dependencies = [ - "bytemuck", -] +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" @@ -202,16 +160,24 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" name = "back" version = "0.1.0" dependencies = [ + "bytes", "chrono", "clap", "gix", + "http", + "http-body-util", + "hyper", + "hyper-util", + "log", "markdown", - "rocket", + "rinja", "rss", "serde", "serde_json", "sha2", + "stderrlog", "thiserror", + "tokio", "url", ] @@ -227,20 +193,23 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] -name = "binascii" -version = "0.1.4" +name = "basic-toml" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" +dependencies = [ + "serde", +] [[package]] name = "bitflags" -version = "2.6.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "block-buffer" @@ -253,50 +222,38 @@ dependencies = [ [[package]] name = "bstr" -version = "1.11.1" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" dependencies = [ "memchr", - "regex-automata 0.4.9", + "regex-automata", "serde", ] [[package]] name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - -[[package]] -name = "bytemuck" -version = "1.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" - -[[package]] -name = "byteorder" -version = "1.5.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "bytesize" -version = "1.3.0" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" +checksum = "2d2c12f985c78475a6b8d629afd0c360260ef34cfef52efccdcfd31972f81c2e" [[package]] name = "cc" -version = "1.2.5" +version = "1.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" +checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" dependencies = [ "shlex", ] @@ -309,23 +266,23 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.39" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.6", + "windows-link", ] [[package]] name = "clap" -version = "4.5.23" +version = "4.5.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" +checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" dependencies = [ "clap_builder", "clap_derive", @@ -333,9 +290,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.23" +version = "4.5.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" +checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" dependencies = [ "anstream", "anstyle", @@ -345,9 +302,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" dependencies = [ "heck", "proc-macro2", @@ -374,17 +331,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] -name = "cookie" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" -dependencies = [ - "percent-encoding", - "time", - "version_check", -] - -[[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -392,9 +338,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] @@ -483,15 +429,6 @@ dependencies = [ ] [[package]] -name = "deranged" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", -] - -[[package]] name = "derive_builder" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -523,39 +460,6 @@ dependencies = [ ] [[package]] -name = "devise" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1d90b0c4c777a2cad215e3c7be59ac7c15adf45cf76317009b7d096d46f651d" -dependencies = [ - "devise_codegen", - "devise_core", -] - -[[package]] -name = "devise_codegen" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71b28680d8be17a570a2334922518be6adc3f58ecc880cbb404eaeb8624fd867" -dependencies = [ - "devise_core", - "quote", -] - -[[package]] -name = "devise_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b035a542cf7abf01f2e3c4d5a7acbaebfefe120ae4efc7bde3df98186e4b8af7" -dependencies = [ - "bitflags", - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn", -] - -[[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -592,12 +496,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] -name = "either" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" - -[[package]] name = "encoding_rs" version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -608,9 +506,9 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" @@ -638,20 +536,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] -name = "figment" -version = "0.10.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" -dependencies = [ - "atomic 0.6.0", - "pear", - "serde", - "toml", - "uncased", - "version_check", -] - -[[package]] name = "filetime" version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -665,9 +549,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.35" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" dependencies = [ "crc32fast", "miniz_oxide", @@ -680,6 +564,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + +[[package]] name = "form_urlencoded" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -689,27 +579,12 @@ dependencies = [ ] [[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] name = "futures-channel" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", - "futures-sink", ] [[package]] @@ -719,12 +594,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -742,28 +611,10 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ - "futures-channel", "futures-core", - "futures-io", - "futures-sink", "futures-task", - "memchr", "pin-project-lite", "pin-utils", - "slab", -] - -[[package]] -name = "generator" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" -dependencies = [ - "cc", - "libc", - "log", - "rustversion", - "windows", ] [[package]] @@ -778,13 +629,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets", ] [[package]] @@ -795,9 +647,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "gix" -version = "0.69.1" +version = "0.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d0eebdaecdcf405d5433a36f85e4f058cf4de48ee2604388be0dbccbaad353e" +checksum = "736f14636705f3a56ea52b553e67282519418d9a35bb1e90b3a9637a00296b68" dependencies = [ "gix-actor", "gix-archive", @@ -855,9 +707,9 @@ dependencies = [ [[package]] name = "gix-actor" -version = "0.33.1" +version = "0.33.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b24171f514cef7bb4dfb72a0b06dacf609b33ba8ad2489d4c4559a03b7afb3" +checksum = "20018a1a6332e065f1fcc8305c1c932c6b8c9985edea2284b3c79dc6fa3ee4b2" dependencies = [ "bstr", "gix-date", @@ -869,9 +721,9 @@ dependencies = [ [[package]] name = "gix-archive" -version = "0.18.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b63ef086543dce4f2cf9cb1ded1216bbd40332d3abcdd8d876e97f7812d9a26" +checksum = "3d22c6ecdb350461a975159ebe514294064b9542a4cbc4a12d00c3f46a1107ce" dependencies = [ "bstr", "gix-date", @@ -883,9 +735,9 @@ dependencies = [ [[package]] name = "gix-attributes" -version = "0.23.1" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddf9bf852194c0edfe699a2d36422d2c1f28f73b7c6d446c3f0ccd3ba232cadc" +checksum = "f151000bf662ef5f641eca6102d942ee31ace80f271a3ef642e99776ce6ddb38" dependencies = [ "bstr", "gix-glob", @@ -900,27 +752,27 @@ dependencies = [ [[package]] name = "gix-bitmap" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d48b897b4bbc881aea994b4a5bbb340a04979d7be9089791304e04a9fbc66b53" +checksum = "b1db9765c69502650da68f0804e3dc2b5f8ccc6a2d104ca6c85bc40700d37540" dependencies = [ "thiserror", ] [[package]] name = "gix-chunk" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6ffbeb3a5c0b8b84c3fe4133a6f8c82fa962f4caefe8d0762eced025d3eb4f7" +checksum = "0b1f1d8764958699dc764e3f727cef280ff4d1bd92c107bbf8acd85b30c1bd6f" dependencies = [ "thiserror", ] [[package]] name = "gix-command" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9405c0a56e17f8365a46870cd2c7db71323ecc8bda04b50cb746ea37bd091e90" +checksum = "cb410b84d6575db45e62025a9118bdbf4d4b099ce7575a76161e898d9ca98df1" dependencies = [ "bstr", "gix-path", @@ -930,9 +782,9 @@ dependencies = [ [[package]] name = "gix-commitgraph" -version = "0.25.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8da6591a7868fb2b6dabddea6b09988b0b05e0213f938dbaa11a03dd7a48d85" +checksum = "e23a8ec2d8a16026a10dafdb6ed51bcfd08f5d97f20fa52e200bc50cb72e4877" dependencies = [ "bstr", "gix-chunk", @@ -944,9 +796,9 @@ dependencies = [ [[package]] name = "gix-config" -version = "0.42.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6649b406ca1f99cb148959cf00468b231f07950f8ec438cc0903cda563606f19" +checksum = "377c1efd2014d5d469e0b3cd2952c8097bce9828f634e04d5665383249f1d9e9" dependencies = [ "bstr", "gix-config-value", @@ -965,9 +817,9 @@ dependencies = [ [[package]] name = "gix-config-value" -version = "0.14.10" +version = "0.14.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49aaeef5d98390a3bcf9dbc6440b520b793d1bf3ed99317dc407b02be995b28e" +checksum = "11365144ef93082f3403471dbaa94cfe4b5e72743bdb9560719a251d439f4cee" dependencies = [ "bitflags", "bstr", @@ -978,9 +830,9 @@ dependencies = [ [[package]] name = "gix-credentials" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a50c56b785c29a151ab4ccf74a83fe4e21d2feda0d30549504b4baed353e0a" +checksum = "cf950f9ee1690bb9c4388b5152baa8a9f41ad61e5cf1ba0ec8c207b08dab9e45" dependencies = [ "bstr", "gix-command", @@ -1007,17 +859,20 @@ dependencies = [ [[package]] name = "gix-diff" -version = "0.49.0" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8e92566eccbca205a0a0f96ffb0327c061e85bc5c95abbcddfe177498aa04f6" +checksum = "62afb7f4ca0acdf4e9dad92065b2eb1bf2993bcc5014b57bc796e3a365b17c4d" dependencies = [ "bstr", + "gix-attributes", "gix-command", "gix-filter", "gix-fs", "gix-hash", + "gix-index", "gix-object", "gix-path", + "gix-pathspec", "gix-tempfile", "gix-trace", "gix-traverse", @@ -1028,9 +883,9 @@ dependencies = [ [[package]] name = "gix-dir" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fba2ffbcf4bd34438e8a8367ccbc94870549903d1f193a14f47eb6b0967e1293" +checksum = "c1d78db3927a12f7d1b788047b84efacaab03ef25738bd1c77856ad8966bd57b" dependencies = [ "bstr", "gix-discover", @@ -1048,9 +903,9 @@ dependencies = [ [[package]] name = "gix-discover" -version = "0.37.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83bf6dfa4e266a4a9becb4d18fc801f92c3f7cc6c433dd86fdadbcf315ffb6ef" +checksum = "d0c2414bdf04064e0f5a5aa029dfda1e663cf9a6c4bfc8759f2d369299bb65d8" dependencies = [ "bstr", "dunce", @@ -1064,9 +919,9 @@ dependencies = [ [[package]] name = "gix-features" -version = "0.39.1" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d85d673f2e022a340dba4713bed77ef2cf4cd737d2f3e0f159d45e0935fd81f" +checksum = "8bfdd4838a8d42bd482c9f0cb526411d003ee94cc7c7b08afe5007329c71d554" dependencies = [ "bytes", "bytesize", @@ -1087,9 +942,9 @@ dependencies = [ [[package]] name = "gix-filter" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0ecdee5667f840ba20c7fe56d63f8e1dc1e6b3bfd296151fe5ef07c874790a" +checksum = "bdcc36cd7dbc63ed0ec3558645886553d1afd3cd09daa5efb9cba9cceb942bbb" dependencies = [ "bstr", "encoding_rs", @@ -1108,9 +963,9 @@ dependencies = [ [[package]] name = "gix-fs" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3d4fac505a621f97e5ce2c69fdc425742af00c0920363ca4074f0eb48b1db9" +checksum = "182e7fa7bfdf44ffb7cfe7451b373cdf1e00870ac9a488a49587a110c562063d" dependencies = [ "fastrand", "gix-features", @@ -1119,9 +974,9 @@ dependencies = [ [[package]] name = "gix-glob" -version = "0.17.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf69a6bec0a3581567484bf99a4003afcaf6c469fd4214352517ea355cf3435" +checksum = "4e9c7249fa0a78f9b363aa58323db71e0a6161fd69860ed6f48dedf0ef3a314e" dependencies = [ "bitflags", "bstr", @@ -1131,9 +986,9 @@ dependencies = [ [[package]] name = "gix-hash" -version = "0.15.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5eccc17194ed0e67d49285e4853307e4147e95407f91c1c3e4a13ba9f4e4ce" +checksum = "e81c5ec48649b1821b3ed066a44efb95f1a268b35c1d91295e61252539fbe9f8" dependencies = [ "faster-hex", "thiserror", @@ -1141,9 +996,9 @@ dependencies = [ [[package]] name = "gix-hashtable" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef65b256631078ef733bc5530c4e6b1c2e7d5c2830b75d4e9034ab3997d18fe" +checksum = "189130bc372accd02e0520dc5ab1cef318dcc2bc829b76ab8d84bbe90ac212d1" dependencies = [ "gix-hash", "hashbrown 0.14.5", @@ -1152,9 +1007,9 @@ dependencies = [ [[package]] name = "gix-ignore" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b1fb24d2a4af0aa7438e2771d60c14a80cf2c9bd55c29cf1712b841f05bb8a" +checksum = "4f529dcb80bf9855c0a7c49f0ac588df6d6952d63a63fefc254b9c869d2cdf6f" dependencies = [ "bstr", "gix-glob", @@ -1165,9 +1020,9 @@ dependencies = [ [[package]] name = "gix-index" -version = "0.37.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "270645fd20556b64c8ffa1540d921b281e6994413a0ca068596f97e9367a257a" +checksum = "acd12e3626879369310fffe2ac61acc828613ef656b50c4ea984dd59d7dc85d8" dependencies = [ "bitflags", "bstr", @@ -1186,16 +1041,16 @@ dependencies = [ "itoa", "libc", "memmap2", - "rustix", + "rustix 0.38.44", "smallvec", "thiserror", ] [[package]] name = "gix-lock" -version = "15.0.1" +version = "16.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd3ab68a452db63d9f3ebdacb10f30dba1fa0d31ac64f4203d395ed1102d940" +checksum = "9739815270ff6940968441824d162df9433db19211ca9ba8c3fc1b50b849c642" dependencies = [ "gix-tempfile", "gix-utils", @@ -1204,9 +1059,9 @@ dependencies = [ [[package]] name = "gix-mailmap" -version = "0.25.1" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6a108b866e00b8a59b8746906cccf2648ffc3e393dc9cca97254dd75c2ddf8c" +checksum = "017996966133afb1e631796d8cf32e43300f8f76233f2a15ce9af5be5069b0a6" dependencies = [ "bstr", "gix-actor", @@ -1216,9 +1071,9 @@ dependencies = [ [[package]] name = "gix-negotiate" -version = "0.17.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d27f830a16405386e9c83b9d5be8261fe32bbd6b3caf15bd1b284c6b2b7ef1a8" +checksum = "a6a8af1ef7bbe303d30b55312b7f4d33e955de43a3642ae9b7347c623d80ef80" dependencies = [ "bitflags", "gix-commitgraph", @@ -1232,9 +1087,9 @@ dependencies = [ [[package]] name = "gix-object" -version = "0.46.1" +version = "0.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42d58010183ef033f31088479b4eb92b44fe341b35b62d39eb8b185573d77ea" +checksum = "ddc4b3a0044244f0fe22347fb7a79cca165e37829d668b41b85ff46a43e5fd68" dependencies = [ "bstr", "gix-actor", @@ -1253,9 +1108,9 @@ dependencies = [ [[package]] name = "gix-odb" -version = "0.66.0" +version = "0.67.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb780eceb3372ee204469478de02eaa34f6ba98247df0186337e0333de97d0ae" +checksum = "3e93457df69cd09573608ce9fa4f443fbd84bc8d15d8d83adecd471058459c1b" dependencies = [ "arc-swap", "gix-date", @@ -1274,9 +1129,9 @@ dependencies = [ [[package]] name = "gix-pack" -version = "0.56.0" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4158928929be29cae7ab97afc8e820a932071a7f39d8ba388eed2380c12c566c" +checksum = "fc13a475b3db735617017fb35f816079bf503765312d4b1913b18cf96f3fa515" dependencies = [ "clru", "gix-chunk", @@ -1293,9 +1148,9 @@ dependencies = [ [[package]] name = "gix-packetline" -version = "0.18.2" +version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "911aeea8b2dabeed2f775af9906152a1f0109787074daf9e64224e3892dde453" +checksum = "c7e5ae6bc3ac160a6bf44a55f5537813ca3ddb08549c0fd3e7ef699c73c439cd" dependencies = [ "bstr", "faster-hex", @@ -1305,9 +1160,9 @@ dependencies = [ [[package]] name = "gix-packetline-blocking" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce9004ce1bc00fd538b11c1ec8141a1558fb3af3d2b7ac1ac5c41881f9e42d2a" +checksum = "c1cbf8767c6abd5a6779f586702b5bcd8702380f4208219449cf1c9d0cd1e17c" dependencies = [ "bstr", "faster-hex", @@ -1317,9 +1172,9 @@ dependencies = [ [[package]] name = "gix-path" -version = "0.10.13" +version = "0.10.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc292ef1a51e340aeb0e720800338c805975724c1dfbd243185452efd8645b7" +checksum = "c40f12bb65a8299be0cfb90fe718e3be236b7a94b434877012980863a883a99f" dependencies = [ "bstr", "gix-trace", @@ -1330,9 +1185,9 @@ dependencies = [ [[package]] name = "gix-pathspec" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c472dfbe4a4e96fcf7efddcd4771c9037bb4fdea2faaabf2f4888210c75b81e" +checksum = "6430d3a686c08e9d59019806faa78c17315fe22ae73151a452195857ca02f86c" dependencies = [ "bitflags", "bstr", @@ -1345,22 +1200,22 @@ dependencies = [ [[package]] name = "gix-prompt" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82433a19aa44688e3bde05c692870eda50b5db053df53ed5ae6d8ea594a6babd" +checksum = "79f2185958e1512b989a007509df8d61dca014aa759a22bee80cfa6c594c3b6d" dependencies = [ "gix-command", "gix-config-value", "parking_lot", - "rustix", + "rustix 0.38.44", "thiserror", ] [[package]] name = "gix-protocol" -version = "0.47.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c84642e8b6fed7035ce9cc449593019c55b0ec1af7a5dce1ab8a0636eaaeb067" +checksum = "6c61bd61afc6b67d213241e2100394c164be421e3f7228d3521b04f48ca5ba90" dependencies = [ "bstr", "gix-date", @@ -1377,9 +1232,9 @@ dependencies = [ [[package]] name = "gix-quote" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a1e282216ec2ab2816cd57e6ed88f8009e634aec47562883c05ac8a7009a63" +checksum = "e49357fccdb0c85c0d3a3292a9f6db32d9b3535959b5471bb9624908f4a066c6" dependencies = [ "bstr", "gix-utils", @@ -1388,9 +1243,9 @@ dependencies = [ [[package]] name = "gix-ref" -version = "0.49.1" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91b61776c839d0f1b7114901179afb0947aa7f4d30793ca1c56d335dfef485f" +checksum = "47adf4c5f933429f8554e95d0d92eee583cfe4b95d2bf665cd6fd4a1531ee20c" dependencies = [ "gix-actor", "gix-features", @@ -1409,9 +1264,9 @@ dependencies = [ [[package]] name = "gix-refspec" -version = "0.27.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00c056bb747868c7eb0aeb352c9f9181ab8ca3d0a2550f16470803500c6c413d" +checksum = "59650228d8f612f68e7f7a25f517fcf386c5d0d39826085492e94766858b0a90" dependencies = [ "bstr", "gix-hash", @@ -1423,9 +1278,9 @@ dependencies = [ [[package]] name = "gix-revision" -version = "0.31.1" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e1ddc474405a68d2ce8485705dd72fe6ce959f2f5fe718601ead5da2c8f9e7" +checksum = "3fe28bbccca55da6d66e6c6efc6bb4003c29d407afd8178380293729733e6b53" dependencies = [ "bitflags", "bstr", @@ -1441,9 +1296,9 @@ dependencies = [ [[package]] name = "gix-revwalk" -version = "0.17.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "510026fc32f456f8f067d8f37c34088b97a36b2229d88a6a5023ef179fcb109d" +checksum = "d4ecb80c235b1e9ef2b99b23a81ea50dd569a88a9eb767179793269e0e616247" dependencies = [ "gix-commitgraph", "gix-date", @@ -1456,9 +1311,9 @@ dependencies = [ [[package]] name = "gix-sec" -version = "0.10.10" +version = "0.10.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8b876ef997a955397809a2ec398d6a45b7a55b4918f2446344330f778d14fd6" +checksum = "d84dae13271f4313f8d60a166bf27e54c968c7c33e2ffd31c48cafe5da649875" dependencies = [ "bitflags", "gix-path", @@ -1468,9 +1323,9 @@ dependencies = [ [[package]] name = "gix-shallow" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2673242e87492cb6ff671f0c01f689061ca306c4020f137197f3abc84ce01" +checksum = "ab72543011e303e52733c85bef784603ef39632ddf47f69723def52825e35066" dependencies = [ "bstr", "gix-hash", @@ -1480,9 +1335,9 @@ dependencies = [ [[package]] name = "gix-status" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1665770e277608bd6b0eaf86adbf6cb3ffc6fb97749e7bc6f9318ac5f37564df" +checksum = "414cc1d85079d7ca32c3ab4a6479bf7e174cd251c74a82339c6cc393da3f4883" dependencies = [ "bstr", "filetime", @@ -1503,9 +1358,9 @@ dependencies = [ [[package]] name = "gix-submodule" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2455f8c0fcb6ebe2a6e83c8f522d30615d763eb2ef7a23c7d929f9476e89f5c" +checksum = "74972fe8d46ac8a09490ae1e843b4caf221c5b157c5ac17057e8e1c38417a3ac" dependencies = [ "bstr", "gix-config", @@ -1518,9 +1373,9 @@ dependencies = [ [[package]] name = "gix-tempfile" -version = "15.0.0" +version = "16.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2feb86ef094cc77a4a9a5afbfe5de626897351bbbd0de3cb9314baf3049adb82" +checksum = "2558f423945ef24a8328c55d1fd6db06b8376b0e7013b1bb476cc4ffdf678501" dependencies = [ "dashmap", "gix-fs", @@ -1534,15 +1389,15 @@ dependencies = [ [[package]] name = "gix-trace" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04bdde120c29f1fc23a24d3e115aeeea3d60d8e65bab92cc5f9d90d9302eb952" +checksum = "7c396a2036920c69695f760a65e7f2677267ccf483f25046977d87e4cb2665f7" [[package]] name = "gix-transport" -version = "0.44.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d91e507a8713cfa2318d5a85d75b36e53a40379cc7eb7634ce400ecacbaf" +checksum = "11187418489477b1b5b862ae1aedbbac77e582f2c4b0ef54280f20cfe5b964d9" dependencies = [ "bstr", "gix-command", @@ -1556,9 +1411,9 @@ dependencies = [ [[package]] name = "gix-traverse" -version = "0.43.1" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed47d648619e23e93f971d2bba0d10c1100e54ef95d2981d609907a8cabac89" +checksum = "2bec70e53896586ef32a3efa7e4427b67308531ed186bb6120fb3eca0f0d61b4" dependencies = [ "bitflags", "gix-commitgraph", @@ -1573,9 +1428,9 @@ dependencies = [ [[package]] name = "gix-url" -version = "0.28.2" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d096fb733ba6bd3f5403dba8bd72bdd8809fe2b347b57844040b8f49c93492d9" +checksum = "29218c768b53dd8f116045d87fec05b294c731a4b2bdd257eeca2084cc150b13" dependencies = [ "bstr", "gix-features", @@ -1587,9 +1442,9 @@ dependencies = [ [[package]] name = "gix-utils" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba427e3e9599508ed98a6ddf8ed05493db114564e338e41f6a996d2e4790335f" +checksum = "ff08f24e03ac8916c478c8419d7d3c33393da9bb41fa4c24455d5406aeefd35f" dependencies = [ "bstr", "fastrand", @@ -1598,9 +1453,9 @@ dependencies = [ [[package]] name = "gix-validate" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd520d09f9f585b34b32aba1d0b36ada89ab7fefb54a8ca3fe37fc482a750937" +checksum = "9eaa01c3337d885617c0a42e92823922a2aea71f4caeace6fe87002bdcadbd90" dependencies = [ "bstr", "thiserror", @@ -1608,9 +1463,9 @@ dependencies = [ [[package]] name = "gix-worktree" -version = "0.38.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756dbbe15188fa22540d5eab941f8f9cf511a5364d5aec34c88083c09f4bea13" +checksum = "6673512f7eaa57a6876adceca6978a501d6c6569a4f177767dc405f8b9778958" dependencies = [ "bstr", "gix-attributes", @@ -1627,9 +1482,9 @@ dependencies = [ [[package]] name = "gix-worktree-state" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "672a5416fae50538a0af0374bf67e0c97a932fd9e9b72f7d4bfd25355967cbe1" +checksum = "86f5e199ad5af972086683bd31d640c82cb85885515bf86d86236c73ce575bf0" dependencies = [ "bstr", "gix-features", @@ -1647,9 +1502,9 @@ dependencies = [ [[package]] name = "gix-worktree-stream" -version = "0.18.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34005eae2c0482eeb840e67bdd317ffe6e34057ea4bd8c910fecaee521db69cf" +checksum = "f61b0463c3cf4d07f2c72a10bdb03a2e4d70a9c26416c639346ad67456834485" dependencies = [ "gix-attributes", "gix-features", @@ -1664,23 +1519,17 @@ dependencies = [ ] [[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - -[[package]] name = "h2" -version = "0.3.26" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" dependencies = [ + "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "futures-util", - "http 0.2.12", + "http", "indexmap", "slab", "tokio", @@ -1703,6 +1552,9 @@ name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "foldhash", +] [[package]] name = "heck" @@ -1712,15 +1564,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hermit-abi" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" [[package]] name = "home" @@ -1733,9 +1579,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.12" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ "bytes", "fnv", @@ -1743,32 +1589,33 @@ dependencies = [ ] [[package]] -name = "http" -version = "1.2.0" +name = "http-body" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "fnv", - "itoa", + "http", ] [[package]] -name = "http-body" -version = "0.4.6" +name = "http-body-util" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", - "http 0.2.12", + "futures-util", + "http", + "http-body", "pin-project-lite", ] [[package]] name = "httparse" -version = "1.9.5" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" @@ -1783,27 +1630,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3b1f728c459d27b12448862017b96ad4767b1ec2ec5e6434e99f1577f085b8" [[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + +[[package]] name = "hyper" -version = "0.14.32" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", - "futures-core", "futures-util", "h2", - "http 0.2.12", + "http", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", "tokio", - "tower-service", - "tracing", - "want", ] [[package]] @@ -1976,32 +1843,24 @@ dependencies = [ [[package]] name = "imara-diff" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc9da1a252bd44cd341657203722352efc9bc0c847d06ea6d2dc1cd1135e0a01" +checksum = "17d34b7d42178945f775e84bc4c36dde7c1c6cdfea656d3354d009056f2bb3d2" dependencies = [ - "ahash", - "hashbrown 0.14.5", + "hashbrown 0.15.2", ] [[package]] name = "indexmap" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown 0.15.2", - "serde", ] [[package]] -name = "inlinable_string" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" - -[[package]] name = "io-close" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2013,13 +1872,13 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.13" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ - "hermit-abi 0.4.0", + "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2030,40 +1889,44 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.1.15" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db69f08d4fb10524cacdb074c10b296299d71274ddbc830a8ee65666867002e9" +checksum = "c04ef77ae73f3cf50510712722f0c4e8b46f5aaa1bf5ffad2ae213e6495e78e5" dependencies = [ "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", "windows-sys 0.59.0", ] [[package]] name = "jiff-tzdb" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91335e575850c5c4c673b9bd467b0e025f164ca59d0564f69d0c2ee0ffad4653" +checksum = "962e1dfe9b2d75a84536cf5bf5eaaa4319aa7906c7160134a22883ac316d5f31" [[package]] name = "jiff-tzdb-platform" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9835f0060a626fe59f160437bc725491a6af23133ea906500027d1bd2f8f4329" +checksum = "a63c62e404e7b92979d2792352d885a7f8f83fd1d0d31eea582d77b2ceca697e" dependencies = [ "jiff-tzdb", ] [[package]] name = "js-sys" -version = "0.3.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", @@ -2079,16 +1942,16 @@ dependencies = [ ] [[package]] -name = "lazy_static" -version = "1.5.0" +name = "libc" +version = "0.2.170" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" [[package]] -name = "libc" -version = "0.2.169" +name = "libm" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libredox" @@ -2103,15 +1966,21 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" [[package]] name = "litemap" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "lock_api" @@ -2125,44 +1994,20 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - -[[package]] -name = "loom" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "serde", - "serde_json", - "tracing", - "tracing-subscriber", -] +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] name = "markdown" -version = "1.0.0-alpha.21" +version = "1.0.0-alpha.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6491e6c702bf7e3b24e769d800746d5f2c06a6c6a2db7992612e0f429029e81" +checksum = "9047e0a37a596d4e15411a1ffbdabe71c328908cb90a721cb9bf8dcf3434e6d2" dependencies = [ "unicode-id", ] [[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata 0.1.10", -] - -[[package]] name = "maybe-async" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2195,10 +2040,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] name = "miniz_oxide" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" dependencies = [ "adler2", ] @@ -2210,52 +2071,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] [[package]] -name = "multer" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" -dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http 1.2.0", - "httparse", - "memchr", - "mime", - "spin", - "tokio", - "tokio-util", - "version_check", -] - -[[package]] name = "never" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91" [[package]] -name = "nu-ansi-term" -version = "0.46.0" +name = "nom" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ - "overload", - "winapi", + "memchr", + "minimal-lexical", ] [[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2265,16 +2101,6 @@ dependencies = [ ] [[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi 0.3.9", - "libc", -] - -[[package]] name = "object" version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2285,15 +2111,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" - -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "parking_lot" @@ -2315,30 +2135,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", -] - -[[package]] -name = "pear" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467" -dependencies = [ - "inlinable_string", - "pear_codegen", - "yansi", -] - -[[package]] -name = "pear_codegen" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147" -dependencies = [ - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn", + "windows-targets", ] [[package]] @@ -2349,9 +2146,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -2361,48 +2158,29 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "portable-atomic" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" - -[[package]] -name = "powerfmt" -version = "0.2.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" [[package]] -name = "ppv-lite86" -version = "0.2.20" +name = "portable-atomic-util" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" dependencies = [ - "zerocopy", + "portable-atomic", ] [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] [[package]] -name = "proc-macro2-diagnostics" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "version_check", - "yansi", -] - -[[package]] name = "prodash" version = "29.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2416,9 +2194,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.37.1" +version = "0.37.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f22f29bdff3987b4d8632ef95fd6424ec7e4e0a57e2f4fc63e489e75357f6a03" +checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003" dependencies = [ "encoding_rs", "memchr", @@ -2426,73 +2204,23 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" dependencies = [ "proc-macro2", ] [[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] name = "redox_syscall" -version = "0.5.8" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" dependencies = [ "bitflags", ] [[package]] -name = "ref-cast" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf0a6f84d5f1d581da8b41b47ec8600871962f2a528115b542b362d4b744931" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2500,17 +2228,8 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", + "regex-automata", + "regex-syntax", ] [[package]] @@ -2521,107 +2240,61 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] -name = "rocket" -version = "0.5.1" +name = "rinja" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a516907296a31df7dc04310e7043b61d71954d703b603cc6867a026d7e72d73f" +checksum = "3dc4940d00595430b3d7d5a01f6222b5e5b51395d1120bdb28d854bb8abb17a5" dependencies = [ - "async-stream", - "async-trait", - "atomic 0.5.3", - "binascii", - "bytes", - "either", - "figment", - "futures", - "indexmap", - "log", - "memchr", - "multer", - "num_cpus", - "parking_lot", - "pin-project-lite", - "rand", - "ref-cast", - "rocket_codegen", - "rocket_http", - "serde", - "state", - "tempfile", - "time", - "tokio", - "tokio-stream", - "tokio-util", - "ubyte", - "version_check", - "yansi", + "humansize", + "itoa", + "percent-encoding", + "rinja_derive", ] [[package]] -name = "rocket_codegen" -version = "0.5.1" +name = "rinja_derive" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "575d32d7ec1a9770108c879fc7c47815a80073f96ca07ff9525a94fcede1dd46" +checksum = "08d9ed0146aef6e2825f1b1515f074510549efba38d71f4554eec32eb36ba18b" dependencies = [ - "devise", - "glob", - "indexmap", + "basic-toml", + "memchr", + "mime", + "mime_guess", "proc-macro2", "quote", - "rocket_http", + "rinja_parser", + "rustc-hash", + "serde", "syn", - "unicode-xid", - "version_check", ] [[package]] -name = "rocket_http" -version = "0.5.1" +name = "rinja_parser" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e274915a20ee3065f611c044bd63c40757396b6dbc057d6046aec27f14f882b9" +checksum = "93f9a866e2e00a7a1fb27e46e9e324a6f7c0e7edc4543cae1d38f4e4a100c610" dependencies = [ - "cookie", - "either", - "futures", - "http 0.2.12", - "hyper", - "indexmap", - "log", "memchr", - "pear", - "percent-encoding", - "pin-project-lite", - "ref-cast", + "nom", "serde", - "smallvec", - "stable-pattern", - "state", - "time", - "tokio", - "uncased", ] [[package]] name = "rss" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531af70fce504d369cf42ac0a9645f5a62a8ea9265de71cfa25087e9f6080c7c" +checksum = "b2107738f003660f0a91f56fd3e3bd3ab5d918b2ddaf1e1ec2136fb1c46f71bf" dependencies = [ "atom_syndication", "derive_builder", @@ -2636,29 +2309,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] name = "rustix" -version = "0.38.42" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dade4812df5c384711475be5fcd8c162555352945401aed22a35bffeab61f657" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.9.2", "windows-sys 0.59.0", ] [[package]] name = "rustversion" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -2670,12 +2362,6 @@ dependencies = [ ] [[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2683,18 +2369,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.216" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.216" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" dependencies = [ "proc-macro2", "quote", @@ -2703,9 +2389,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.134" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -2714,15 +2400,6 @@ dependencies = [ ] [[package]] -name = "serde_spanned" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" -dependencies = [ - "serde", -] - -[[package]] name = "sha1_smol" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2740,15 +2417,6 @@ dependencies = [ ] [[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] name = "shell-words" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2790,9 +2458,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" [[package]] name = "socket2" @@ -2805,42 +2473,31 @@ dependencies = [ ] [[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[package]] -name = "stable-pattern" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045" -dependencies = [ - "memchr", -] - -[[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] -name = "state" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b8c4a4445d81357df8b1a650d0d0d6fbbbfe99d064aa5e02f3e4022061476d8" -dependencies = [ - "loom", -] - -[[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] +name = "stderrlog" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c910772f992ab17d32d6760e167d2353f4130ed50e796752689556af07dc6b" +dependencies = [ + "chrono", + "is-terminal", + "log", + "termcolor", + "thread_local", +] + +[[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2848,9 +2505,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.91" +version = "2.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" +checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" dependencies = [ "proc-macro2", "quote", @@ -2870,31 +2527,41 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.14.0" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567" dependencies = [ "cfg-if", "fastrand", + "getrandom", "once_cell", - "rustix", + "rustix 1.0.1", "windows-sys 0.59.0", ] [[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] name = "thiserror" -version = "2.0.9" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.9" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", @@ -2912,37 +2579,6 @@ dependencies = [ ] [[package]] -name = "time" -version = "0.3.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - -[[package]] -name = "time-macros" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] name = "tinystr" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2954,9 +2590,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" dependencies = [ "tinyvec_macros", ] @@ -2969,16 +2605,15 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.42.0" +version = "1.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +checksum = "9975ea0f48b5aa3972bf2d888c238182458437cc2a19374b81b25cdf1023fb3a" dependencies = [ "backtrace", "bytes", "libc", "mio", "pin-project-lite", - "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.52.0", @@ -2986,9 +2621,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", @@ -2996,17 +2631,6 @@ dependencies = [ ] [[package]] -name = "tokio-stream" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] name = "tokio-util" version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3020,126 +2644,29 @@ dependencies = [ ] [[package]] -name = "toml" -version = "0.8.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] name = "tracing" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", - "tracing-attributes", "tracing-core", ] [[package]] -name = "tracing-attributes" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] name = "tracing-core" version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", ] [[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] name = "typenum" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "ubyte" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f720def6ce1ee2fc44d40ac9ed6d3a59c361c80a75a7aa8e75bb9baed31cf2ea" -dependencies = [ - "serde", -] +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "uluru" @@ -3151,14 +2678,10 @@ dependencies = [ ] [[package]] -name = "uncased" -version = "0.9.10" +name = "unicase" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" -dependencies = [ - "serde", - "version_check", -] +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-bom" @@ -3174,9 +2697,9 @@ checksum = "10103c57044730945224467c09f71a4db0071c123a0648cc3e818913bde6b561" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-normalization" @@ -3188,12 +2711,6 @@ dependencies = [ ] [[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] name = "url" version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3224,12 +2741,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - -[[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3246,36 +2757,37 @@ dependencies = [ ] [[package]] -name = "want" -version = "0.3.1" +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.13.3+wasi-0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] [[package]] name = "wasm-bindgen" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", @@ -3287,9 +2799,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3297,9 +2809,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", @@ -3310,9 +2822,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "winapi" @@ -3346,30 +2861,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] name = "windows-core" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] +name = "windows-link" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" + +[[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -3378,22 +2890,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-targets", ] [[package]] @@ -3402,48 +2899,30 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" @@ -3456,62 +2935,47 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.20" +version = "0.6.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "1e90edd2ac1aa278a5c4599b1d89cf03074b610800f866d4026dc199d7929a28" dependencies = [ "memchr", ] [[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags", +] + +[[package]] name = "write16" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3524,15 +2988,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] -name = "yansi" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" -dependencies = [ - "is-terminal", -] - -[[package]] name = "yoke" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3562,7 +3017,6 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "byteorder", "zerocopy-derive", ] @@ -3579,18 +3033,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", diff --git a/pkgs/by-name/ba/back/Cargo.toml b/pkgs/by-name/ba/back/Cargo.toml index f1abe32..7fabeed 100644 --- a/pkgs/by-name/ba/back/Cargo.toml +++ b/pkgs/by-name/ba/back/Cargo.toml @@ -22,16 +22,24 @@ license = "AGPL-3.0-or-later" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -chrono = "0.4.39" -clap = { version = "4.5.23", features = ["derive"] } -gix = "0.69.1" -markdown = "1.0.0-alpha.21" -rocket = "0.5.1" -rss = "2.0.11" -serde = "1.0.216" -serde_json = "1.0.134" +bytes = "1.10.1" +chrono = "0.4.40" +clap = { version = "4.5.31", features = ["derive"] } +gix = "0.70.0" +http = "1.2.0" +http-body-util = "0.1.2" +hyper = { version = "1.6.0", features = ["http1", "http2", "server"] } +hyper-util = { version = "0.1.10", features = ["tokio"] } +log = "0.4.26" +markdown = "1.0.0-alpha.23" +rinja = "0.3.5" +rss = "2.0.12" +serde = "1.0.218" +serde_json = "1.0.140" sha2 = "0.10.8" -thiserror = "2.0.9" +stderrlog = "0.6.0" +thiserror = "2.0.12" +tokio = { version = "1.44.0", features = ["macros", "net", "rt-multi-thread"] } url = { version = "2.5.4", features = ["serde"] } [profile.release] diff --git a/pkgs/by-name/ba/back/README.md b/pkgs/by-name/ba/back/README.md index 4bbd9c0..222ccf3 100644 --- a/pkgs/by-name/ba/back/README.md +++ b/pkgs/by-name/ba/back/README.md @@ -17,29 +17,60 @@ If not, see <https://www.gnu.org/licenses/agpl.txt>. ## Usage -Currently, `back` only visualizes a `git-bug` repository. As such it takes exactly one -argument, being the repository to visualize. -The server is than started at `http://127.0.0.1:8000` and provides access to the issues -(bugs) tracked via `git-bug`. +`back` is modelled after `cgit`, only for `git-bug` initialized repositories. The server is than +started at `http://127.0.0.1:8000` and provides access to the issues (bugs) tracked via `git-bug`, +via multiple routes: -### Note +### `/` + +The default index is a list of all repositories that have `git-bug` data in them. + +### `<repo_path>/issues/<state>` + +This path displays all issues in `<state>` (i.e., open or closed) for the repository at +`<repo_path>`. + +### `<repo_path>/issue/<issue_id>` + +Displays the actual issue with `id` `<issue_id>`. Beware, that the `<isuse_id>` is sourced from the +actual git object associated with the issue create commit. As such, it is not the same ID, as +displayed by the `git-bug` CLI. + +### `<repo_path>/issues/feed` + +An RSS feed usable to subscribe to. This includes all issues and all comments of issues. -`back` needs write access to the repository, because of internal `gix` and `git` object -reasons. +## Configuration file -## Relevant Environment Variables +The config file is passed to `back` via the first command line argument. It is written in JSON. +An example configuration file is available at [`./contrib/config.json`](./contrib/config.json). -### `ROCKET_PORT` +Following keys are required: -> Default: 8000 +### `source_code_repository_url` -This is the port the server binds to. +The URL to the source code of this instance of `back`. -### `SOURCE_CODE_REPOSITORY_URL` +### `root_url` + +The root URL this instance of `back` is hosted at. For example: `https://issues.foss-syndicate.org`. +This is required by the RSS feed to generate links to the various issues/comments. + +### `scan_path` + +The path under which to search for the repositories as specified by the `projects.list` file. This +is semantically the same as `cgit`'s `scan-path`. + +### `project_list` + +The path to the file specifying the repositories to search. A repository path per line. This is +semantically the same as `cgit`'s `project-list`. + +### Note -The URL to the back's source. +`back` needs write access to the repository, because of internal `gix` and `git` object reasons. ## Licensing -This project complies with the REUSE v3.3 specification. This means that every file -clearly states its copyright. +This project complies with the REUSE v3.3 specification. This means that every file clearly states +its copyright. diff --git a/pkgs/by-name/ba/back/assets/style.css b/pkgs/by-name/ba/back/assets/style.css index b789f17..3cf4a00 100644 --- a/pkgs/by-name/ba/back/assets/style.css +++ b/pkgs/by-name/ba/back/assets/style.css @@ -17,9 +17,9 @@ * It was originally licensed under the MIT license. */ -input[type="text"], -input[type="password"], -textarea{ +input[type='text'], +input[type='password'], +textarea { width: 100%; padding: 0.5rem; outline: none; @@ -30,18 +30,18 @@ textarea{ margin-bottom: 1rem; } -textarea{ +textarea { resize: vertical; } -input[type="submit"]{ +input[type='submit'] { -webkit-appearance: none; border: none; cursor: pointer; font-size: 1rem; } -input[type="submit"]{ +input[type='submit'] { background-color: var(--success); padding: 0.5rem; text-decoration: none; @@ -52,37 +52,35 @@ input[type="submit"]{ transition: box-shadow 0.15s ease-in-out; } -input[type="submit"]:hover{ - -moz-box-shadow: 0.25rem 0.25rem 0 0 rgba(0,0,0,0.08); - -o-box-shadow: 0.25rem 0.25rem 0 0 rgba(0,0,0,0.08); - -webkit-box-shadow: 0.25rem 0.25rem 0 0 rgba(0,0,0,0.08); - -ms-box-shadow: 0.25rem 0.25rem 0 0 rgba(0,0,0,0.08); - box-shadow: 0.25rem 0.25rem 0 0 rgba(0,0,0,0.08); +input[type='submit']:hover { + -moz-box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); + -o-box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); + -webkit-box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); + -ms-box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); + box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); } -input[type="submit"]:active, -input[type="submit"]:focus{ - -moz-box-shadow: 0.1rem 0.1rem 0 0 rgba(0,0,0,0.05); - -o-box-shadow: 0.1rem 0.1rem 0 0 rgba(0,0,0,0.05); - -webkit-box-shadow: 0.1rem 0.1rem 0 0 rgba(0,0,0,0.05); - -ms-box-shadow: 0.1rem 0.1rem 0 0 rgba(0,0,0,0.05); - box-shadow: 0.1rem 0.1rem 0 0 rgba(0,0,0,0.05); +input[type='submit']:active, +input[type='submit']:focus { + -moz-box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); + -o-box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); + -webkit-box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); + -ms-box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); + box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); outline: none; border: none; } - - -.form-link input[type="submit"]{ +.form-link input[type='submit'] { background-color: initial; color: inherit; padding: 0; text-decoration: underline; } -.form-link input[type="submit"]:hover, -.form-link input[type="submit"]:active, -.form-link input[type="submit"]:focus{ +.form-link input[type='submit']:hover, +.form-link input[type='submit']:active, +.form-link input[type='submit']:focus { -moz-box-shadow: 0 0 0 0; -o-box-shadow: 0 0 0 0; -webkit-box-shadow: 0 0 0 0; @@ -90,49 +88,49 @@ input[type="submit"]:focus{ box-shadow: 0 0 0 0; } -.form-group{ +.form-group { margin-top: 1rem; } -label.checkbox{ +label.checkbox { cursor: pointer; } -.issue-list{ +.issue-list { list-style-type: none; padding-left: 0; } -.issue-list .issue-subject{ +.issue-list .issue-subject { font-weight: bold; } -.issue-list li{ +.issue-list li { padding-bottom: 1rem; } -.issue-list li + li{ +.issue-list li + li { border-top: 1px solid var(--gray); } -.issue-list a{ +.issue-list a { text-decoration: none; display: block; } -.issue-list a:hover{ +.issue-list a:hover { outline: none; } -.issue-list a:hover .issue-subject{ +.issue-list a:hover .issue-subject { color: var(--primary); } -.comment-count{ +.comment-count { color: var(--gray); } -.issue-links{ +.issue-links { display: flex; flex-direction: row; align-items: center; @@ -140,9 +138,7 @@ label.checkbox{ flex-wrap: wrap; } - - -.issue-search input[type="search"]{ +.issue-search input[type='search'] { padding: 0.5rem; background-image: url('static/search.png'); background-position: 10px 10px; @@ -152,13 +148,13 @@ label.checkbox{ border: 1px solid var(--gray); } -.issue-info{ +.issue-info { display: flex; justify-content: space-between; align-items: center; } -.issue-info .edit-issue{ +.issue-info .edit-issue { background-color: var(--success); padding: 0.5rem; text-decoration: none; @@ -169,111 +165,111 @@ label.checkbox{ transition: box-shadow 0.15s ease-in-out; } -.issue-info .edit-issue:hover{ - -moz-box-shadow: 0.25rem 0.25rem 0 0 rgba(0,0,0,0.08); - -o-box-shadow: 0.25rem 0.25rem 0 0 rgba(0,0,0,0.08); - -webkit-box-shadow: 0.25rem 0.25rem 0 0 rgba(0,0,0,0.08); - -ms-box-shadow: 0.25rem 0.25rem 0 0 rgba(0,0,0,0.08); - box-shadow: 0.25rem 0.25rem 0 0 rgba(0,0,0,0.08); +.issue-info .edit-issue:hover { + -moz-box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); + -o-box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); + -webkit-box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); + -ms-box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); + box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); } .issue-info .edit-issue:active, -.issue-info .edit-issue:focus{ - -moz-box-shadow: 0.1rem 0.1rem 0 0 rgba(0,0,0,0.05); - -o-box-shadow: 0.1rem 0.1rem 0 0 rgba(0,0,0,0.05); - -webkit-box-shadow: 0.1rem 0.1rem 0 0 rgba(0,0,0,0.05); - -ms-box-shadow: 0.1rem 0.1rem 0 0 rgba(0,0,0,0.05); - box-shadow: 0.1rem 0.1rem 0 0 rgba(0,0,0,0.05); +.issue-info .edit-issue:focus { + -moz-box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); + -o-box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); + -webkit-box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); + -ms-box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); + box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); outline: none; border: none; } -.issue-info .created-by-at{ +.issue-info .created-by-at { flex: 1; } -.issue-info .edit-issue{ +.issue-info .edit-issue { background-color: var(--light) -gray; flex: 0; margin-right: 0.5rem; } -.issue-info .close-issue{ +.issue-info .close-issue { background-color: var(--failure); } -.issue-history{ +.issue-history { list-style: none; border-top: 1px solid var(--gray); padding-top: 1rem; padding-left: 2rem; } -.issue-history .comment-info{ +.issue-history .comment-info { color: var(--gray); margin: 0; padding-top: 1rem; } -.issue-history .comment-info a{ +.issue-history .comment-info a { text-decoration: none; } -.issue-history .comment-info a:hover{ +.issue-history .comment-info a:hover { text-decoration: underline; } .issue-history .comment, -.issue-history .event{ +.issue-history .event { padding-top: 1rem; padding-bottom: 1rem; border-bottom: 1px solid var(--gray); } .issue-history .comment p, -.issue-history .event p{ +.issue-history .event p { margin: 0; } .issue-history .comment:target, -.issue-history .event:target{ +.issue-history .event:target { border-color: var(--primary); border-bottom-width: 3px; } -.issue-history .event{ +.issue-history .event { color: var(--gray); } -blockquote{ +blockquote { border-left: 5px solid var(--light) -gray; padding-left: 1rem; margin-left: 0rem; } -pre{ +pre { overflow-x: auto; } -body{ +body { font-family: sans-serif; color: var(--text); background: var(--bg); --text: rgb(24, 24, 24); --bg: white; - --gray: #8D8D8D; + --gray: #8d8d8d; --primary: rgb(106, 154, 255); --primary-light: rgb(150, 166, 200); --success: rgb(168, 249, 166); --failure: rgb(247, 167, 167); - --light-gray: #EEE; + --light-gray: #eee; } -@media (prefers-color-scheme: dark){ - body{ +@media (prefers-color-scheme: dark) { + body { --text: rgb(240, 240, 240); --bg: black; - --gray: #8D8D8D; + --gray: #8d8d8d; --primary: rgb(106, 154, 255); --primary-light: rgb(150, 166, 200); --success: rgb(14, 130, 11); @@ -282,54 +278,54 @@ body{ } } -a{ +a { color: inherit; } -.content{ +.content { max-width: 800px; margin: 0 auto; } -header{ +header { display: flex; align-items: center; border-bottom: 1px solid var(--text); margin-bottom: 1rem; } -header h1{ +header h1 { padding: 0; flex: 1; } -header .issue-number{ +header .issue-number { color: var(--gray); font-size: 1.5rem; } -nav{ +nav { display: flex; color: var(--gray); justify-content: space-between; } -nav .nav-group{ +nav .nav-group { display: flex; } -nav .nav-group >*{ +nav .nav-group > * { margin-left: 0.5rem; } -footer{ +footer { border-top: 1px solid var(--gray); padding-top: 1rem; margin-top: 1rem; color: var(--gray); } -.new-issue{ +.new-issue { background-color: var(--success); padding: 0.5rem; text-decoration: none; @@ -340,41 +336,41 @@ footer{ transition: box-shadow 0.15s ease-in-out; } -.new-issue:hover{ - -moz-box-shadow: 0.25rem 0.25rem 0 0 rgba(0,0,0,0.08); - -o-box-shadow: 0.25rem 0.25rem 0 0 rgba(0,0,0,0.08); - -webkit-box-shadow: 0.25rem 0.25rem 0 0 rgba(0,0,0,0.08); - -ms-box-shadow: 0.25rem 0.25rem 0 0 rgba(0,0,0,0.08); - box-shadow: 0.25rem 0.25rem 0 0 rgba(0,0,0,0.08); +.new-issue:hover { + -moz-box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); + -o-box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); + -webkit-box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); + -ms-box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); + box-shadow: 0.25rem 0.25rem 0 0 rgba(0, 0, 0, 0.08); } .new-issue:active, -.new-issue:focus{ - -moz-box-shadow: 0.1rem 0.1rem 0 0 rgba(0,0,0,0.05); - -o-box-shadow: 0.1rem 0.1rem 0 0 rgba(0,0,0,0.05); - -webkit-box-shadow: 0.1rem 0.1rem 0 0 rgba(0,0,0,0.05); - -ms-box-shadow: 0.1rem 0.1rem 0 0 rgba(0,0,0,0.05); - box-shadow: 0.1rem 0.1rem 0 0 rgba(0,0,0,0.05); +.new-issue:focus { + -moz-box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); + -o-box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); + -webkit-box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); + -ms-box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); + box-shadow: 0.1rem 0.1rem 0 0 rgba(0, 0, 0, 0.05); outline: none; border: none; } -.alert{ +.alert { padding: 0.5rem; margin-bottom: 1rem; background-color: var(--failure); } -.login-form{ +.login-form { max-width: 300px; margin: 0 auto; } -.created-by-at{ +.created-by-at { color: var(--gray); } -.sr-only{ +.sr-only { border: 0; clip: rect(0 0 0 0); height: 1px; diff --git a/pkgs/by-name/ba/back/contrib/config.json b/pkgs/by-name/ba/back/contrib/config.json index 2347bf2..81d1041 100644 --- a/pkgs/by-name/ba/back/contrib/config.json +++ b/pkgs/by-name/ba/back/contrib/config.json @@ -1,5 +1,6 @@ { "source_code_repository_url": "https://git.foss-syndicate.org/vhack.eu/nixos-server/tree/pkgs/by-name/ba/back", - "repository_path": "/path/to/your/repository" - "root_url": "https://issues.foss-syndicate.org" + "root_url": "https://issues.foss-syndicate.org", + "scan_path": "/path/to/the/scan/path", + "project_list": "/path/to/the/projects.list" } diff --git a/pkgs/by-name/ba/back/flake.lock b/pkgs/by-name/ba/back/flake.lock index ca4a6b9..9e20410 100644 --- a/pkgs/by-name/ba/back/flake.lock +++ b/pkgs/by-name/ba/back/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1734435836, - "narHash": "sha256-kMBQ5PRiFLagltK0sH+08aiNt3zGERC2297iB6vrvlU=", + "lastModified": 1741310760, + "narHash": "sha256-aizILFrPgq/W53Jw8i0a1h1GZAAKtlYOrG/A5r46gVM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "4989a246d7a390a859852baddb1013f825435cee", + "rev": "de0fe301211c267807afd11b12613f5511ff7433", "type": "github" }, "original": { diff --git a/pkgs/by-name/ba/back/package.nix b/pkgs/by-name/ba/back/package.nix index faa0e1d..d70052b 100644 --- a/pkgs/by-name/ba/back/package.nix +++ b/pkgs/by-name/ba/back/package.nix @@ -21,7 +21,8 @@ rustPlatform.buildRustPackage { filter = name: type: (type == "directory") || (builtins.elem (builtins.baseNameOf name) ["Cargo.toml" "Cargo.lock" "style.css"]) - || (lib.strings.hasSuffix ".rs" (builtins.baseNameOf name)); + || (lib.strings.hasSuffix ".rs" (builtins.baseNameOf name)) + || (lib.strings.hasSuffix ".html" (builtins.baseNameOf name)); }; doCheck = true; diff --git a/pkgs/by-name/ba/back/src/config/mod.rs b/pkgs/by-name/ba/back/src/config/mod.rs index 7351ad8..832d060 100644 --- a/pkgs/by-name/ba/back/src/config/mod.rs +++ b/pkgs/by-name/ba/back/src/config/mod.rs @@ -18,55 +18,119 @@ use gix::ThreadSafeRepository; use serde::Deserialize; use url::Url; -use crate::error::{self, Error}; +use crate::{ + error::{self, Error}, + git_bug::dag::is_git_bug, +}; +#[derive(Deserialize)] pub struct BackConfig { - // NOTE(@bpeetz): We do not need to html escape this, as the value must be a valid url. As such - // `<tags>` of all kinds _should_ be invalid. <2024-12-26> + /// The url to the source code of back. This is needed, because back is licensed under the + /// AGPL. pub source_code_repository_url: Url, - pub repository: ThreadSafeRepository, - pub root: Url, + + /// The root url this instance of back is hosted on. + /// For example: + /// `issues.foss-syndicate.org` + pub root_url: Url, + + project_list: PathBuf, + + /// The path that is the common parent of all the repositories. + pub scan_path: PathBuf, } -#[derive(Deserialize)] -struct RawBackConfig { - source_code_repository_url: Url, - repository_path: PathBuf, - root_url: Url, +pub struct BackRepositories { + repositories: Vec<BackRepository>, } -impl BackConfig { - pub fn from_config_file(path: &Path) -> error::Result<Self> { - let value = fs::read_to_string(path).map_err(|err| Error::ConfigRead { - file: path.to_owned(), - error: err, - })?; +impl BackRepositories { + pub fn iter(&self) -> <&Self as IntoIterator>::IntoIter { + self.into_iter() + } +} - let raw: RawBackConfig = - serde_json::from_str(&value).map_err(|err| Error::ConfigParse { - file: path.to_owned(), - error: err, - })?; +impl<'a> IntoIterator for &'a BackRepositories { + type Item = <&'a Vec<BackRepository> as IntoIterator>::Item; + + type IntoIter = <&'a Vec<BackRepository> as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.repositories.iter() + } +} - Self::try_from(raw) +impl BackRepositories { + /// Try to get the repository at path `path`. + /// If no repository was registered/found at `path`, returns an error. + pub fn get(&self, path: &Path) -> Result<&BackRepository, error::Error> { + self.repositories + .iter() + .find(|p| p.repo_path == path) + .ok_or(error::Error::RepoFind { + repository_path: path.to_owned(), + }) } } -impl TryFrom<RawBackConfig> for BackConfig { - type Error = error::Error; +pub struct BackRepository { + repo_path: PathBuf, +} - fn try_from(value: RawBackConfig) -> Result<Self, Self::Error> { - let repository = { - ThreadSafeRepository::open(&value.repository_path).map_err(|err| Error::RepoOpen { - repository_path: value.repository_path, - error: Box::new(err), +impl BackRepository { + pub fn open(&self, scan_path: &Path) -> Result<ThreadSafeRepository, error::Error> { + let path = { + let base = scan_path.join(&self.repo_path); + if base.is_dir() { + base + } else { + PathBuf::from(base.display().to_string() + ".git") + } + }; + let repo = ThreadSafeRepository::open(path).map_err(|err| Error::RepoOpen { + repository_path: self.repo_path.to_owned(), + error: Box::new(err), + })?; + if is_git_bug(&repo.to_thread_local())? { + Ok(repo) + } else { + Err(error::Error::NotGitBug { + path: self.repo_path.clone(), }) - }?; + } + } + pub fn path(&self) -> &Path { + &self.repo_path + } +} + +impl BackConfig { + pub fn repositories(&self) -> error::Result<BackRepositories> { + let repositories = fs::read_to_string(&self.project_list) + .map_err(|err| error::Error::ProjectListRead { + error: err, + file: self.project_list.to_owned(), + })? + .lines() + .try_fold(vec![], |mut acc, path| { + acc.push(BackRepository { + repo_path: PathBuf::from(path), + }); + + Ok::<_, error::Error>(acc) + })?; + Ok(BackRepositories { repositories }) + } + + pub fn from_config_file(path: &Path) -> error::Result<Self> { + let value = fs::read_to_string(path).map_err(|err| Error::ConfigRead { + file: path.to_owned(), + error: err, + })?; - Ok(Self { - repository, - source_code_repository_url: value.source_code_repository_url, - root: value.root_url, + serde_json::from_str(&value).map_err(|err| Error::ConfigParse { + file: path.to_owned(), + error: err, }) } } diff --git a/pkgs/by-name/ba/back/src/error/mod.rs b/pkgs/by-name/ba/back/src/error/mod.rs index 8b71700..8889033 100644 --- a/pkgs/by-name/ba/back/src/error/mod.rs +++ b/pkgs/by-name/ba/back/src/error/mod.rs @@ -9,37 +9,53 @@ // You should have received a copy of the License along with this program. // If not, see <https://www.gnu.org/licenses/agpl.txt>. -use std::{fmt::Display, io, path::PathBuf}; +use std::{fmt::Display, io, net::SocketAddr, path::PathBuf}; +use gix::hash::Prefix; use thiserror::Error; -use crate::web::prefix::BackPrefix; - pub type Result<T> = std::result::Result<T, Error>; -pub mod responder; - #[derive(Error, Debug)] pub enum Error { ConfigParse { file: PathBuf, error: serde_json::Error, }, + + ProjectListRead { + file: PathBuf, + error: io::Error, + }, ConfigRead { file: PathBuf, error: io::Error, }, - RocketLaunch(#[from] rocket::Error), - + NotGitBug { + path: PathBuf, + }, RepoOpen { repository_path: PathBuf, error: Box<gix::open::Error>, }, + RepoFind { + repository_path: PathBuf, + }, RepoRefsIter(#[from] gix::refs::packed::buffer::open::Error), - RepoRefsPrefixed(#[from] std::io::Error), + RepoRefsPrefixed { + error: io::Error, + }, + + TcpBind { + addr: SocketAddr, + err: io::Error, + }, + TcpAccept { + err: io::Error, + }, IssuesPrefixMissing { - prefix: BackPrefix, + prefix: Prefix, }, IssuesPrefixParse(#[from] gix::hash::prefix::from_hex::Error), } @@ -54,6 +70,13 @@ impl Display for Error { file.display() ) } + Error::ProjectListRead { file, error } => { + write!( + f, + "while trying to read the project.list file ({}): {error}", + file.display() + ) + } Error::ConfigRead { file, error } => { write!( f, @@ -61,9 +84,6 @@ impl Display for Error { file.display() ) } - Error::RocketLaunch(error) => { - write!(f, "while trying to start back: {error}") - } Error::RepoOpen { repository_path, error, @@ -74,10 +94,24 @@ impl Display for Error { repository_path.display() ) } + Error::NotGitBug { path } => { + write!( + f, + "Repository ('{}') has no initialized git-bug data", + path.display() + ) + } + Error::RepoFind { repository_path } => { + write!( + f, + "failed to find the repository at path: '{}'", + repository_path.display() + ) + } Error::RepoRefsIter(error) => { write!(f, "while iteration over the refs in a repository: {error}",) } - Error::RepoRefsPrefixed(error) => { + Error::RepoRefsPrefixed { error, .. } => { write!(f, "while prefixing the refs with a path: {error}") } Error::IssuesPrefixMissing { prefix } => { @@ -89,6 +123,12 @@ impl Display for Error { Error::IssuesPrefixParse(error) => { write!(f, "The given prefix can not be parsed as prefix: {error}") } + Error::TcpBind { addr, err } => { + write!(f, "while trying to open tcp {addr} for listening: {err}.") + } + Error::TcpAccept { err } => { + write!(f, "while trying to accept a tcp connection: {err}.") + } } } } diff --git a/pkgs/by-name/ba/back/src/error/responder.rs b/pkgs/by-name/ba/back/src/error/responder.rs deleted file mode 100644 index 7bea961..0000000 --- a/pkgs/by-name/ba/back/src/error/responder.rs +++ /dev/null @@ -1,23 +0,0 @@ -// Back - An extremely simple git issue tracking system. Inspired by tvix's -// panettone -// -// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de> -// SPDX-License-Identifier: AGPL-3.0-or-later -// -// This file is part of Back. -// -// You should have received a copy of the License along with this program. -// If not, see <https://www.gnu.org/licenses/agpl.txt>. - -use rocket::{ - response::{self, Responder, Response}, - Request, -}; - -use super::Error; - -impl<'r> Responder<'r, 'static> for Error { - fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> { - Response::build_from(self.to_string().respond_to(req)?).ok() - } -} diff --git a/pkgs/by-name/ba/back/src/git_bug/dag/mod.rs b/pkgs/by-name/ba/back/src/git_bug/dag/mod.rs index 9c158a7..3d22b04 100644 --- a/pkgs/by-name/ba/back/src/git_bug/dag/mod.rs +++ b/pkgs/by-name/ba/back/src/git_bug/dag/mod.rs @@ -123,11 +123,23 @@ impl Dag { } } +/// Check whether `git-bug` has been initialized in this repository +pub fn is_git_bug(repo: &Repository) -> error::Result<bool> { + Ok(repo + .refs + .iter()? + .prefixed(Path::new("refs/bugs/")) + .map_err(|err| error::Error::RepoRefsPrefixed { error: err })? + .count() + > 0) +} + pub fn issues_from_repository(repo: &Repository) -> error::Result<Vec<Dag>> { let dags = repo .refs .iter()? - .prefixed(Path::new("refs/bugs/"))? + .prefixed(Path::new("refs/bugs/")) + .map_err(|err| error::Error::RepoRefsPrefixed { error: err })? .map(|val| { let reference = val.expect("All `git-bug` references in 'refs/bugs' should be objects"); diff --git a/pkgs/by-name/ba/back/src/git_bug/format/mod.rs b/pkgs/by-name/ba/back/src/git_bug/format/mod.rs index b3b6bcc..ffe44fd 100644 --- a/pkgs/by-name/ba/back/src/git_bug/format/mod.rs +++ b/pkgs/by-name/ba/back/src/git_bug/format/mod.rs @@ -16,8 +16,8 @@ use markdown::to_html; use serde::Deserialize; use serde_json::Value; -#[derive(Debug, Default, Clone)] /// Markdown content. +#[derive(Debug, Default, Clone)] pub struct MarkDown { value: String, } @@ -88,6 +88,19 @@ pub struct HtmlString { value: String, } +impl From<String> for HtmlString { + fn from(value: String) -> Self { + Self { value } + } +} +impl From<&str> for HtmlString { + fn from(value: &str) -> Self { + Self { + value: value.to_owned(), + } + } +} + impl From<MarkDown> for HtmlString { fn from(value: MarkDown) -> Self { Self { value: value.value } diff --git a/pkgs/by-name/ba/back/src/git_bug/issue/mod.rs b/pkgs/by-name/ba/back/src/git_bug/issue/mod.rs index f27bfec..d382b54 100644 --- a/pkgs/by-name/ba/back/src/git_bug/issue/mod.rs +++ b/pkgs/by-name/ba/back/src/git_bug/issue/mod.rs @@ -128,7 +128,7 @@ impl RawCollapsedIssue { } => { self.id = Some(entity.id.clone()); self.author = Some(entity.author.clone()); - self.timestamp = Some(timestamp.clone()); + self.timestamp = Some(timestamp); self.title = Some(title); self.message = Some(message); self.status = Some(Status::Open); // This is the default in git_bug diff --git a/pkgs/by-name/ba/back/src/main.rs b/pkgs/by-name/ba/back/src/main.rs index 961c39b..61953c4 100644 --- a/pkgs/by-name/ba/back/src/main.rs +++ b/pkgs/by-name/ba/back/src/main.rs @@ -9,14 +9,11 @@ // You should have received a copy of the License along with this program. // If not, see <https://www.gnu.org/licenses/agpl.txt>. -use std::process; +use std::{process, sync::Arc}; use clap::Parser; -use config::BackConfig; -use rocket::routes; -use web::feed; -use crate::web::{closed, open, show_issue, styles}; +use crate::config::BackConfig; mod cli; pub mod config; @@ -25,7 +22,7 @@ pub mod git_bug; mod web; fn main() -> Result<(), String> { - if let Err(err) = rocket_main() { + if let Err(err) = server_main() { eprintln!("Error {err}"); process::exit(1); } else { @@ -33,20 +30,24 @@ fn main() -> Result<(), String> { } } -#[rocket::main] -async fn rocket_main() -> Result<(), error::Error> { +#[tokio::main] +async fn server_main() -> Result<(), error::Error> { let args = cli::Cli::parse(); + stderrlog::new() + .module(module_path!()) + .modules(["hyper", "http"]) + .quiet(false) + .show_module_names(false) + .color(stderrlog::ColorChoice::Auto) + .verbosity(2) + .timestamp(stderrlog::Timestamp::Off) + .init() + .expect("Let's just hope that this does not panic"); + let config = BackConfig::from_config_file(&args.config_file)?; - rocket::build() - .mount("/", routes![open, closed, show_issue, styles, feed]) - .manage(config) - .ignite() - .await - .expect("This error should only happen on a miss-configuration.") - .launch() - .await?; + web::main(Arc::new(config)).await?; Ok(()) } diff --git a/pkgs/by-name/ba/back/src/web/generate/mod.rs b/pkgs/by-name/ba/back/src/web/generate/mod.rs new file mode 100644 index 0000000..ae783a3 --- /dev/null +++ b/pkgs/by-name/ba/back/src/web/generate/mod.rs @@ -0,0 +1,225 @@ +use std::{fs, path::Path}; + +use gix::hash::Prefix; +use log::info; +use rinja::Template; +use url::Url; + +use crate::{ + config::BackConfig, + error, + git_bug::{ + dag::issues_from_repository, + issue::{CollapsedIssue, Status}, + }, +}; + +#[derive(Template)] +#[template(path = "./issues.html")] +struct IssuesTemplate { + wanted_status: Status, + counter_status: Status, + issues: Vec<CollapsedIssue>, + + /// The path to the repository + repo_path: String, + + /// The URL to `back`'s source code + source_code_repository_url: Url, +} +pub fn issues( + config: &BackConfig, + wanted_status: Status, + counter_status: Status, + repo_path: &Path, +) -> error::Result<String> { + let repository = config + .repositories()? + .get(repo_path)? + .open(&config.scan_path)?; + + let mut issue_list = issues_from_repository(&repository.to_thread_local())? + .into_iter() + .map(|issue| issue.collapse()) + .filter(|issue| issue.status == wanted_status) + .collect::<Vec<CollapsedIssue>>(); + + // Sort by date descending. + // SAFETY: + // The time stamp is only used for sorting, so a malicious attacker could only affect the issue + // sorting. + issue_list.sort_by_key(|issue| unsafe { issue.timestamp.to_unsafe() }); + issue_list.reverse(); + + Ok(IssuesTemplate { + wanted_status, + counter_status, + source_code_repository_url: config.source_code_repository_url.clone(), + issues: issue_list, + repo_path: repo_path.display().to_string(), + } + .render() + .expect("This should always work")) +} + +use crate::git_bug::format::HtmlString; +#[derive(Template)] +#[template(path = "./issue.html")] +struct IssueTemplate { + issue: CollapsedIssue, + + /// The path to the repository + repo_path: String, + + /// The URL to `back`'s source code + source_code_repository_url: Url, +} +pub fn issue(config: &BackConfig, repo_path: &Path, prefix: Prefix) -> error::Result<String> { + let repository = config + .repositories()? + .get(repo_path)? + .open(&config.scan_path)? + .to_thread_local(); + + let maybe_issue = issues_from_repository(&repository)? + .into_iter() + .map(|val| val.collapse()) + .find(|issue| issue.id.to_string().starts_with(&prefix.to_string())); + + match maybe_issue { + Some(issue) => Ok(IssueTemplate { + issue, + repo_path: repo_path.display().to_string(), + source_code_repository_url: config.source_code_repository_url.clone(), + } + .render() + .expect("This should always work")), + None => Err(error::Error::IssuesPrefixMissing { prefix }), + } +} + +#[derive(Template)] +#[template(path = "./repos.html")] +struct ReposTemplate { + repos: Vec<RepoValue>, + + /// The URL to `back`'s source code + source_code_repository_url: Url, +} +struct RepoValue { + description: String, + owner: String, + path: String, +} +pub fn repos(config: &BackConfig) -> error::Result<String> { + let repos: Vec<RepoValue> = config + .repositories()? + .iter() + .filter_map(|raw_repo| match raw_repo.open(&config.scan_path) { + Ok(repo) => { + let repo = repo.to_thread_local(); + let git_config = repo.config_snapshot(); + + let path = raw_repo.path().to_string_lossy().to_string(); + + let owner = git_config + .string("cgit.owner") + .map(|v| v.to_string()) + .unwrap_or("<No owner>".to_owned()); + + let description = fs::read_to_string(repo.git_dir().join("description")) + .unwrap_or("<No description>".to_owned()); + + Some(RepoValue { + description, + owner, + path, + }) + } + Err(err) => { + info!( + "Repo '{}' could not be opened: '{err}'", + raw_repo.path().display() + ); + None + } + }) + .collect(); + + Ok(ReposTemplate { + repos, + source_code_repository_url: config.source_code_repository_url.clone(), + } + .render() + .expect("this should work")) +} + +pub fn feed(config: &BackConfig, repo_path: &Path) -> error::Result<String> { + use rss::{ChannelBuilder, Item, ItemBuilder}; + + let repository = config + .repositories()? + .get(repo_path)? + .open(&config.scan_path)? + .to_thread_local(); + + let issues: Vec<CollapsedIssue> = issues_from_repository(&repository)? + .into_iter() + .map(|issue| issue.collapse()) + .collect(); + + // Collect all Items as rss items + let mut items: Vec<Item> = issues + .iter() + .map(|issue| { + ItemBuilder::default() + .title(issue.title.to_string()) + .author(issue.author.to_string()) + .description(issue.message.to_string()) + .pub_date(issue.timestamp.to_string()) + .link(format!( + "/{}/{}/issue/{}", + repo_path.display(), + &config.root_url, + issue.id + )) + .build() + }) + .collect(); + + // Append all comments after converting them to rss items + items.extend( + issues + .iter() + .filter(|issue| !issue.comments.is_empty()) + .flat_map(|issue| { + issue + .comments + .iter() + .map(|comment| { + ItemBuilder::default() + .title(issue.title.to_string()) + .author(comment.author.to_string()) + .description(comment.message.to_string()) + .pub_date(comment.timestamp.to_string()) + .link(format!( + "/{}/{}/issue/{}", + repo_path.display(), + &config.root_url, + issue.id + )) + .build() + }) + .collect::<Vec<Item>>() + }) + .collect::<Vec<Item>>(), + ); + + let channel = ChannelBuilder::default() + .title("Issues") + .link(config.root_url.to_string()) + .description(format!("The rss feed for issues on {}.", &config.root_url)) + .items(items) + .build(); + Ok(channel.to_string()) +} diff --git a/pkgs/by-name/ba/back/src/web/issue_html.rs b/pkgs/by-name/ba/back/src/web/issue_html.rs deleted file mode 100644 index 45c0281..0000000 --- a/pkgs/by-name/ba/back/src/web/issue_html.rs +++ /dev/null @@ -1,166 +0,0 @@ -// Back - An extremely simple git issue tracking system. Inspired by tvix's -// panettone -// -// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de> -// SPDX-License-Identifier: AGPL-3.0-or-later -// -// This file is part of Back. -// -// You should have received a copy of the License along with this program. -// If not, see <https://www.gnu.org/licenses/agpl.txt>. - -use rocket::response::content::RawHtml; - -use crate::{ - config::BackConfig, - git_bug::{ - format::HtmlString, - issue::{identity::Author, CollapsedIssue, Comment}, - }, -}; - -impl CollapsedIssue { - pub fn to_list_entry(&self) -> RawHtml<String> { - let comment_list = if self.comments.is_empty() { - String::new() - } else { - let comments_string = if self.comments.len() > 1 { - "comments" - } else { - "comment" - }; - - format!( - r#" - <span class="comment-count"> - {} {}</span> - "#, - self.comments.len(), - comments_string - ) - }; - - let CollapsedIssue { - id, - title, - message: _, - author, - timestamp, - comments: _, - status: _, - last_status_change: _, - labels: _, - } = self; - - let Author { name, email, id: _ } = author; - - RawHtml(format!( - r#" - <li> - <a href="/issue/{id}"> - <p> - <span class="issue-subject">{title}</span> - </p> - <span class="issue-number">{id}</span> - <span class="created-by-at">Opened by <span class="user-name">{name}</span> <span class="user-email"><{email}></span> at <span class="timestamp">{timestamp}</span></span>{comment_list} </a> - </li> -"#, - )) - } - - pub fn to_html(&self, config: &BackConfig) -> RawHtml<String> { - let comments = if self.comments.is_empty() { - String::new() - } else { - let fmt_comments: String = self - .comments - .iter() - .map(|val| { - let Comment { - id, - author, - message, - timestamp, - } = val; - let Author { - name, - email: _, - id: _, - } = author; - - format!( - r#" - <li class="comment" id="{id}"> - {message} - <p class="comment-info"><span class="user-name">{name} at {timestamp}</span></p> - </li> - "#, - ) - }) - .collect::<Vec<String>>() - .join("\n"); - - format!( - r#" - <ol class="issue-history"> - {fmt_comments} - </ol> - "# - ) - }; - - { - let CollapsedIssue { - id, - title, - message, - author, - timestamp, - comments: _, - status: _, - last_status_change: _, - labels: _, - } = self; - let Author { name, email, id: _ } = author; - let html_title = HtmlString::from(title.clone()); - - RawHtml(format!( - r#" -<!DOCTYPE html> -<html lang="en"> - <head> - <title>{html_title} | Back</title> - <link href="/style.css" rel="stylesheet" type="text/css"> - <meta content="width=device-width,initial-scale=1" name="viewport"> - </head> - <body> - <div class="content"> - <nav> - <a href="/issues/open">Open Issues</a> - <a href="/issues/closed">Closed Issues</a> - </nav> - <header> - <h1>{title}</h1> - <div class="issue-number">{id}</div> - </header> - <main> - <div class="issue-info"> - <span class="created-by-at">Opened by <span class="user-name">{name}</span> <span class="user-email"><{email}></span> at <span class="timestamp">{timestamp}</span></span> - </div> - {message} - {comments} - </main> - <footer> - <nav> - <a href="/issues/open">Open Issues</a> - <a href="{}">Source code</a> - <a href="/issues/closed">Closed Issues</a> - </nav> - </footer> - </div> - </body> -</html> -"#, - config.source_code_repository_url - )) - } - } -} diff --git a/pkgs/by-name/ba/back/src/web/mod.rs b/pkgs/by-name/ba/back/src/web/mod.rs index f7a4077..cc087ab 100644 --- a/pkgs/by-name/ba/back/src/web/mod.rs +++ b/pkgs/by-name/ba/back/src/web/mod.rs @@ -1,186 +1,127 @@ -// Back - An extremely simple git issue tracking system. Inspired by tvix's -// panettone -// -// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de> -// SPDX-License-Identifier: AGPL-3.0-or-later -// -// This file is part of Back. -// -// You should have received a copy of the License along with this program. -// If not, see <https://www.gnu.org/licenses/agpl.txt>. - -use crate::{ - config::BackConfig, - error::{self, Error}, - git_bug::{ - dag::issues_from_repository, - issue::{CollapsedIssue, Status}, - }, -}; -use prefix::BackPrefix; -use rocket::{ - get, - response::content::{RawCss, RawHtml}, - State, -}; - -mod issue_html; -pub mod prefix; - -#[get("/style.css")] -pub fn styles() -> RawCss<String> { - RawCss(include_str!("../../assets/style.css").to_owned()) -} +use bytes::Bytes; +use http_body_util::combinators::BoxBody; +use hyper::{server::conn::http1, service::service_fn, Method, Request, Response, StatusCode}; +use hyper_util::rt::TokioIo; +use log::{error, info}; +use responses::{html_response, html_response_status, html_response_status_content_type}; +use tokio::net::TcpListener; + +use std::{convert::Infallible, net::SocketAddr, path::PathBuf, sync::Arc}; + +use crate::{config::BackConfig, error, git_bug::issue::Status}; + +mod generate; +mod responses; + +async fn match_uri( + config: Arc<BackConfig>, + req: Request<hyper::body::Incoming>, +) -> Result<Response<BoxBody<Bytes, Infallible>>, hyper::Error> { + if req.method() != Method::GET { + return Ok(html_response_status( + "Only get requests are supported", + StatusCode::NOT_ACCEPTABLE, + )); + } -pub fn issue_list_boilerplate( - config: &State<BackConfig>, - wanted_status: Status, - counter_status: Status, -) -> error::Result<RawHtml<String>> { - let repository = &config.repository; - - let mut issue_list = issues_from_repository(&repository.to_thread_local())? - .into_iter() - .map(|issue| issue.collapse()) - .collect::<Vec<CollapsedIssue>>(); - - // Sort by date descending. - issue_list.sort_by_key(|issue| unsafe { issue.timestamp.to_unsafe() }); - issue_list.reverse(); - - let issue_list_str = issue_list.into_iter().fold(String::new(), |acc, issue| { - format!("{}{}", acc, { - if issue.status == wanted_status { - let issue_entry = issue.to_list_entry(); - issue_entry.0 - } else { - String::new() + let output = || -> Result<Response<BoxBody<Bytes, Infallible>>, error::Error> { + match req.uri().path().trim_end_matches("/") { + "" => Ok(html_response(generate::repos(&config)?)), + + "/style.css" => Ok(responses::html_response_status_content_type( + include_str!("../../assets/style.css"), + StatusCode::OK, + "text/css", + )), + + path if path.ends_with("/issues/open") => { + let repo_path = PathBuf::from( + path.strip_suffix("/issues/open") + .expect("This suffix exists") + .strip_prefix("/") + .expect("This also exists"), + ); + + let issues = generate::issues(&config, Status::Open, Status::Closed, &repo_path)?; + Ok(html_response(issues)) + } + path if path.ends_with("/issues/closed") => { + let repo_path = PathBuf::from( + path.strip_suffix("/issues/closed") + .expect("This suffix exists") + .strip_prefix("/") + .expect("This also exists"), + ); + + let issues = generate::issues(&config, Status::Closed, Status::Open, &repo_path)?; + Ok(html_response(issues)) + } + path if path.ends_with("/issues/feed") => { + let repo_path = PathBuf::from( + path.strip_suffix("/issues/feed") + .expect("This suffix exists") + .strip_prefix("/") + .expect("This also exists"), + ); + + let feed = generate::feed(&config, &repo_path)?; + Ok(html_response_status_content_type( + feed, + StatusCode::OK, + "text/xml", + )) } - }) - }); - - let counter_status_lower = counter_status.to_string().to_lowercase(); - Ok(RawHtml(format!( - r#" - <!DOCTYPE html> - <html lang="en"> - <head> - <title>Back</title> - <link href="/style.css" rel="stylesheet" type="text/css"> - <meta content="width=device-width,initial-scale=1" name="viewport"> - </head> - <body> - <div class="content"> - <header> - <h1>{wanted_status} Issues</h1> - </header> - <main> - <div class="issue-links"> - <a href="/issues/{counter_status_lower}/">View {counter_status} issues</a> - <a href="{}">Source code</a> - <!-- - <form class="issue-search" method="get"> - <input name="search" title="Issue search query" type="search"> - <input class="sr-only" type="submit" value="Search Issues"> - </form> - --> - </div> - <ol class="issue-list"> - {issue_list_str} - </ol> - </main> - </div> - </body> - </html> - "#, - config.source_code_repository_url - ))) -} -#[get("/issues/open")] -pub fn open(config: &State<BackConfig>) -> error::Result<RawHtml<String>> { - issue_list_boilerplate(config, Status::Open, Status::Closed) -} -#[get("/issues/closed")] -pub fn closed(config: &State<BackConfig>) -> error::Result<RawHtml<String>> { - issue_list_boilerplate(config, Status::Closed, Status::Open) -} + path if path.contains("/issue/") => { + let (repo_path, prefix) = { + let split: Vec<&str> = path.split("/issue/").collect(); + + let prefix = + gix::hash::Prefix::from_hex(split[1]).map_err(error::Error::from)?; + + let repo_path = + PathBuf::from(split[0].strip_prefix("/").expect("This prefix exists")); -#[get("/issues/feed")] -pub fn feed(config: &State<BackConfig>) -> error::Result<RawHtml<String>> { - use rss::{ChannelBuilder, Item, ItemBuilder}; - - //Collect all Items as rss items - let mut items: Vec<Item> = issues_from_repository(&config.repository.to_thread_local())? - .into_iter() - .map(|issue| issue.collapse()) - .map(|issue| { - ItemBuilder::default() - .title(issue.title.to_string()) - .author(issue.author.to_string()) - .description(issue.message.to_string()) - .pub_date(issue.timestamp.to_string()) - .link(format!("{}/issue/{}", &config.root.to_string(), issue.id)) - .build() - }) - .collect(); - //Append all comments after converting them to rss items - items.extend( - issues_from_repository(&config.repository.to_thread_local())? - .into_iter() - .map(|issue| issue.collapse()) - .filter(|issue| issue.comments.len() > 0) - .map(|issue| { - issue - .comments - .into_iter() - .map(|comment| { - ItemBuilder::default() - .title(issue.title.to_string()) - .author(comment.author.to_string()) - .description(comment.message.to_string()) - .pub_date(comment.timestamp.to_string()) - .link(format!("{}/issue/{}", &config.root.to_string(), issue.id)) - .build() - }) - .collect::<Vec<Item>>() - }) - .flatten() - .collect::<Vec<Item>>(), - ); - - let channel = ChannelBuilder::default() - .title("Issues") - .link(config.root.to_string()) - .description(format!("The rss feed for issues on {}.", config.root)) - .items(items) - .build(); - Ok(RawHtml(channel.to_string())) + (repo_path, prefix) + }; + Ok(html_response(generate::issue(&config, &repo_path, prefix)?)) + } + + other => Ok(responses::html_response_status_content_type( + format!("'{}' not found", other), + StatusCode::NOT_FOUND, + "text/plain", + )), + } + }; + match output() { + Ok(response) => Ok(response), + Err(err) => Ok(err.into_response()), + } } -#[get("/issue/<prefix>")] -pub fn show_issue( - config: &State<BackConfig>, - prefix: Result<BackPrefix, gix::hash::prefix::from_hex::Error>, -) -> error::Result<RawHtml<String>> { - // NOTE(@bpeetz): Explicitly unwrap the `prefix` here (instead of taking the unwrapped value as - // argument), to avoid triggering rockets "errors forward to the next route" feature. - // This ensures, that our error message actually reaches the user. <2024-12-26> - let prefix = prefix?; - - let repository = config.repository.to_thread_local(); - - let all_issues: Vec<CollapsedIssue> = issues_from_repository(&repository)? - .into_iter() - .map(|val| val.collapse()) - .collect(); - - let maybe_issue = all_issues - .iter() - .find(|issue| issue.id.to_string().starts_with(&prefix.to_string())); - - match maybe_issue { - Some(issue) => Ok(issue.to_html(config)), - None => Err(Error::IssuesPrefixMissing { prefix }), +pub async fn main(config: Arc<BackConfig>) -> Result<(), error::Error> { + let addr: SocketAddr = ([127, 0, 0, 1], 8000).into(); + + let listener = TcpListener::bind(addr) + .await + .map_err(|err| error::Error::TcpBind { addr, err })?; + info!("Listening on http://{}", addr); + loop { + let (stream, _) = listener + .accept() + .await + .map_err(|err| error::Error::TcpAccept { err })?; + let io = TokioIo::new(stream); + + let local_config = Arc::clone(&config); + + let service = service_fn(move |req| match_uri(Arc::clone(&local_config), req)); + + tokio::task::spawn(async move { + if let Err(err) = http1::Builder::new().serve_connection(io, service).await { + error!("Error serving connection: {:?}", err); + } + }); } } diff --git a/pkgs/by-name/ba/back/src/web/prefix.rs b/pkgs/by-name/ba/back/src/web/prefix.rs deleted file mode 100644 index 5143799..0000000 --- a/pkgs/by-name/ba/back/src/web/prefix.rs +++ /dev/null @@ -1,35 +0,0 @@ -// Back - An extremely simple git issue tracking system. Inspired by tvix's -// panettone -// -// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de> -// SPDX-License-Identifier: AGPL-3.0-or-later -// -// This file is part of Back. -// -// You should have received a copy of the License along with this program. -// If not, see <https://www.gnu.org/licenses/agpl.txt>. - -use std::fmt::Display; - -use gix::hash::Prefix; -use rocket::request::FromParam; - -#[derive(Debug)] -pub struct BackPrefix { - prefix: Prefix, -} -impl Display for BackPrefix { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.prefix.fmt(f) - } -} - -impl<'a> FromParam<'a> for BackPrefix { - type Error = gix::hash::prefix::from_hex::Error; - - fn from_param(param: &'a str) -> Result<Self, Self::Error> { - let prefix = Prefix::from_hex(param)?; - - Ok(Self { prefix }) - } -} diff --git a/pkgs/by-name/ba/back/src/web/responses.rs b/pkgs/by-name/ba/back/src/web/responses.rs new file mode 100644 index 0000000..e50f8c2 --- /dev/null +++ b/pkgs/by-name/ba/back/src/web/responses.rs @@ -0,0 +1,50 @@ +use std::convert::Infallible; + +use bytes::Bytes; +use http::{Response, StatusCode, Version}; +use http_body_util::{combinators::BoxBody, BodyExt, Full}; + +use crate::{error, git_bug::format::HtmlString}; + +pub(super) fn html_response<T: Into<Bytes>>(html_text: T) -> Response<BoxBody<Bytes, Infallible>> { + html_response_status(html_text, StatusCode::OK) +} + +pub(super) fn html_response_status<T: Into<Bytes>>( + html_text: T, + status: StatusCode, +) -> Response<BoxBody<Bytes, Infallible>> { + html_response_status_content_type(html_text, status, "text/html") +} + +pub(super) fn html_response_status_content_type<T: Into<Bytes>>( + html_text: T, + status: StatusCode, + content_type: &str, +) -> Response<BoxBody<Bytes, Infallible>> { + Response::builder() + .status(status) + .version(Version::HTTP_2) + .header("Content-Type", format!("{}; charset=utf-8", content_type)) + .header("x-content-type-options", "nosniff") + .header("x-frame-options", "SAMEORIGIN") + .body(full(html_text)) + .expect("This will always build") +} + +fn full<T: Into<Bytes>>(chunk: T) -> BoxBody<Bytes, Infallible> { + Full::new(chunk.into()).boxed() +} + +// FIXME: Not all errors should return `INTERNAL_SERVER_ERROR`. <2025-03-08> +impl error::Error { + pub fn into_response(self) -> Response<BoxBody<Bytes, Infallible>> { + html_response_status( + format!( + "<h1> Internal server error. </h1> <pre>Error: {}</pre>", + HtmlString::from(self.to_string()) + ), + StatusCode::INTERNAL_SERVER_ERROR, + ) + } +} diff --git a/pkgs/by-name/ba/back/templates/issue.html b/pkgs/by-name/ba/back/templates/issue.html new file mode 100644 index 0000000..5b452c5 --- /dev/null +++ b/pkgs/by-name/ba/back/templates/issue.html @@ -0,0 +1,57 @@ +<!doctype html> +<html lang="en"> + <head> + <title>{{ HtmlString::from(issue.title.clone()) }} | Back</title> + <link + href="/style.css" + rel="stylesheet" + type="text/css" /> + <meta + content="width=device-width,initial-scale=1" + name="viewport" /> + </head> + <body> + <div class="content"> + <nav> + <a href="/{{repo_path}}/issues/open">Open Issues</a> + <a href="/{{repo_path}}/issues/closed">Closed Issues</a> + </nav> + <header> + <h1>{{issue.title|safe}}</h1> + <div class="issue-number">{{issue.id}}</div> + </header> + <main> + <div class="issue-info"> + <span class="created-by-at" + >Opened by <span class="user-name">{{issue.author.name|safe}}</span> + <span class="user-email"><{{issue.author.email|safe}}></span> at + <span class="timestamp">{{issue.timestamp}}</span></span + > + </div> + {{issue.message|safe}} {% if !issue.comments.is_empty() %} + <ol class="issue-history"> + {% for comment in issue.comments %} + <li + class="comment" + id="{{comment.id}}"> + {{comment.message|safe}} + <p class="comment-info"> + <span class="user-name" + >{{comment.author.name|safe}} at {{comment.timestamp}}</span + > + </p> + </li> + {% endfor %} + </ol> + {% endif %} + </main> + <footer> + <nav> + <a href="/{{repo_path}}/issues/open">Open Issues</a> + <a href="{{source_code_repository_url}}">Source code</a> + <a href="/{{repo_path}}/issues/closed">Closed Issues</a> + </nav> + </footer> + </div> + </body> +</html> diff --git a/pkgs/by-name/ba/back/templates/issues.html b/pkgs/by-name/ba/back/templates/issues.html new file mode 100644 index 0000000..b6cc9b8 --- /dev/null +++ b/pkgs/by-name/ba/back/templates/issues.html @@ -0,0 +1,60 @@ +<!doctype html> +<html lang="en"> + <head> + <title>Back</title> + <link + href="/style.css" + rel="stylesheet" + type="text/css" /> + <meta + content="width=device-width,initial-scale=1" + name="viewport" /> + </head> + <body> + <div class="content"> + <header> + <h1>{{wanted_status}} Issues</h1> + </header> + <main> + <div class="issue-links"> + <a href="/{{repo_path}}/issues/{{counter_status|lowercase}}/" + >View {{counter_status}} issues</a + > + <a href="{{source_code_repository_url}}">Source code</a> + <!-- + <form class="issue-search" method="get"> + <input name="search" title="Issue search query" type="search"> + <input class="sr-only" type="submit" value="Search Issues"> + </form> + --> + </div> + <ol class="issue-list"> + {% for issue in issues -%} + <li> + <a href="/{{repo_path}}/issue/{{issue.id}}"> + <p> + <span class="issue-subject">{{issue.title|safe}}</span> + </p> + <span class="issue-number">{{issue.id}}</span> + <span class="created-by-at" + >Opened by {{ " " }} + <span class="user-name">{{issue.author.name|safe}}</span> + {{ " " }} + <span class="user-email"><{{issue.author.email|safe}}></span> + {{ "at" }} + <span class="timestamp">{{issue.timestamp}}</span> + </span> + {% if !issue.comments.is_empty() +%} + <span class="comment-count"> + - {{issue.comments.len()}} + comment{{issue.comments.len()|pluralize}}</span + > + {%+ endif %} + </a> + </li> + {%- endfor %} + </ol> + </main> + </div> + </body> +</html> diff --git a/pkgs/by-name/ba/back/templates/repos.html b/pkgs/by-name/ba/back/templates/repos.html new file mode 100644 index 0000000..dbccba0 --- /dev/null +++ b/pkgs/by-name/ba/back/templates/repos.html @@ -0,0 +1,47 @@ +<!doctype html> +<html lang="en"> + <head> + <title>Back</title> + <link + href="/style.css" + rel="stylesheet" + type="text/css" /> + <meta + content="width=device-width,initial-scale=1" + name="viewport" /> + </head> + <body> + <div class="content"> + <header> + <h1>Repositories</h1> + </header> + <main> + <div class="issue-links"> + <a href="{{source_code_repository_url}}">Source code</a> + <!-- + <form class="issue-search" method="get"> + <input name="search" title="Issue search query" type="search"> + <input class="sr-only" type="submit" value="Search Issues"> + </form> + --> + </div> + <ol class="issue-list"> + {% for repo in repos -%} + <li> + <a href="/{{repo.path}}/issues/open"> + <p> + <span class="issue-subject">{{repo.path}}</span> + </p> + <span class="created-by-at"> + <span class="timestamp">{{repo.description}}</span> + {{ "-" }} + <span class="user-name">{{repo.owner}}</span> + </span> + </a> + </li> + {%- endfor %} + </ol> + </main> + </div> + </body> +</html> diff --git a/pkgs/by-name/ba/back/update.sh b/pkgs/by-name/ba/back/update.sh index c715a63..11bd23a 100755 --- a/pkgs/by-name/ba/back/update.sh +++ b/pkgs/by-name/ba/back/update.sh @@ -11,5 +11,5 @@ # You should have received a copy of the License along with this program. # If not, see <https://www.gnu.org/licenses/agpl.txt>. -[ "$1" = "upgrade" ] && cargo upgrade +[ "$1" = "upgrade" ] && cargo upgrade --incompatible cargo update diff --git a/pkgs/by-name/fe/fetchmail-common-name/package.nix b/pkgs/by-name/fe/fetchmail-common-name/package.nix new file mode 100644 index 0000000..9e89bed --- /dev/null +++ b/pkgs/by-name/fe/fetchmail-common-name/package.nix @@ -0,0 +1,15 @@ +{ + pkgs, + callPackage, +}: +pkgs.fetchmail.overrideAttrs (final: prev: { + pname = "fetchmail-common-name"; + + patches = + (prev.patches or []) + ++ [ + ./patches/fix-socket.c-Correctly-check-the-common-name-even-if.patch + ]; + + meta.mainProgram = prev.meta.mainProgram or "fetchmail"; +}) diff --git a/pkgs/by-name/fe/fetchmail-common-name/patches/fix-socket.c-Correctly-check-the-common-name-even-if.patch b/pkgs/by-name/fe/fetchmail-common-name/patches/fix-socket.c-Correctly-check-the-common-name-even-if.patch new file mode 100644 index 0000000..aa17799 --- /dev/null +++ b/pkgs/by-name/fe/fetchmail-common-name/patches/fix-socket.c-Correctly-check-the-common-name-even-if.patch @@ -0,0 +1,40 @@ +From 77a13d5625890d6a0dc3ec312b9d237e61791033 Mon Sep 17 00:00:00 2001 +From: Benedikt Peetz <benedikt.peetz@b-peetz.de> +Date: Sat, 1 Mar 2025 18:20:35 +0100 +Subject: [PATCH] fix(socket.c): Correctly check the common name, even if not + set (only SAN) + +--- + socket.c | 9 --------- + 1 file changed, 9 deletions(-) + +diff --git a/socket.c b/socket.c +index 42b8f1a5..c81bca63 100644 +--- a/socket.c ++++ b/socket.c +@@ -728,7 +728,6 @@ static int SSL_verify_callback(int ok_return, X509_STORE_CTX *ctx, const int str + _depth0ck = 1; + } + +- if ((i = X509_NAME_get_text_by_NID(subj, NID_commonName, buf, sizeof(buf))) != -1) { + if (_ssl_server_cname != NULL) { + char *p1 = buf; + char *p2 = _ssl_server_cname; +@@ -779,14 +778,6 @@ static int SSL_verify_callback(int ok_return, X509_STORE_CTX *ctx, const int str + report(stderr, GT_("Server name not set, could not verify certificate!\n")); + if (strict) return (0); + } +- } else { +- if (outlevel >= O_VERBOSE) +- report(stdout, GT_("Unknown Server CommonName\n")); +- if (ok_return && strict) { +- report(stderr, GT_("Server name not specified in certificate!\n")); +- return (0); +- } +- } + /* Print the finger print. Note that on errors, we might print it more than once + * normally; we kluge around that by using a global variable. */ + if (_check_fp == 1) { +-- +2.47.2 + diff --git a/pkgs/by-name/st/stalwart-mail-free/mail-send.nix b/pkgs/by-name/st/stalwart-mail-free/mail-send.nix new file mode 100644 index 0000000..e0d8c57 --- /dev/null +++ b/pkgs/by-name/st/stalwart-mail-free/mail-send.nix @@ -0,0 +1,20 @@ +{ + stdenv, + fetchFromGitHub, +}: +stdenv.mkDerivation (finalAttrs: { + pname = "mail-send"; + version = "0.5.0"; + + src = fetchFromGitHub { + owner = "stalwartlabs"; + repo = "mail-send"; + tag = "v${finalAttrs.version}"; + hash = "sha256-uDD4GLwjRpNqjtXPMask0twGW2Gcm1PFyDGXcfPS0F4="; + }; + + installPhase = '' + mkdir --parents "$out" + cp --recursive ./. "$out/" + ''; +}) diff --git a/pkgs/by-name/st/stalwart-mail-free/package.nix b/pkgs/by-name/st/stalwart-mail-free/package.nix new file mode 100644 index 0000000..bb2c1db --- /dev/null +++ b/pkgs/by-name/st/stalwart-mail-free/package.nix @@ -0,0 +1,77 @@ +{ + pkgsUnstable, + callPackage, + nixLib, +}: let + spamfilter = callPackage ./spam-filter.nix {}; + + mail-send = callPackage ./mail-send.nix {}; + + # Need to use the newer `rustPlatform` + inherit (pkgsUnstable) rustPlatform; +in + pkgsUnstable.stalwart-mail.override { + rustPlatform = + rustPlatform + // { + buildRustPackage = prev: + rustPlatform.buildRustPackage ( + prev + // { + pname = "stalwart-mail-free"; + passthru = nixLib.warnMerge (prev.passthru or {}) { + inherit spamfilter; + } "stalwart-mail passthru"; + + useFetchCargoVendor = true; + cargoHash = "sha256-Qg01QXP/ImRCUw3aXcZbnM1hysHUwozCdQ7LecjUa0o="; + + # The tests should check if this works. + # And this shaves of around 50% of the build time. + doCheck = false; + + buildNoDefaultFeatures = true; + buildFeatures = [ + "rocks" + "redis" + ]; + + postUnpack = + (prev.postUnpack or "") + + '' + cp --recursive "${mail-send}" ./source/crates/mail-send + chmod -R +w "./source/crates/mail-send" + ''; + + cargoPatches = + (prev.cargoPatches or []) + ++ [ + # `stalwart-mail` does enable their `enterprise` feature per default. + # We want a AGPL only build (i.e., without unfree dependencies), therefore disable the + # `enterprise` feature here. + # We cannot use the `buildFeatures` attribute because it does not actually change the + # correct features. As such we simply patch the correct `Cargo.toml` file. + ./patches/crates-main-Cargo.toml-Use-libre-features.patch + + # `stalwart-mail` uses their bundled store, which makes it impossible to use our + # own CA certificate (e.g., for tests). Thus use a native version. + ./patches/crates-Use-the-platform-CA-bundle-instead-of-the-bun.patch + ]; + + # Check that the enterprise feature is really disabled. + postCheck = + (prev.postCheck or "") + + + # bash + '' + if grep "enterprise" ./target/*/release/stalwart-mail.d; then + echo "ERROR: Proprietary 'enterprise' feature active." + exit 1 + fi + ''; + + meta.mainProgram = prev.meta.mainProgram or "stalwart-mail"; + } + ); + }; + } diff --git a/pkgs/by-name/st/stalwart-mail-free/patches/crates-Use-the-platform-CA-bundle-instead-of-the-bun.patch b/pkgs/by-name/st/stalwart-mail-free/patches/crates-Use-the-platform-CA-bundle-instead-of-the-bun.patch new file mode 100644 index 0000000..e6c3d4b --- /dev/null +++ b/pkgs/by-name/st/stalwart-mail-free/patches/crates-Use-the-platform-CA-bundle-instead-of-the-bun.patch @@ -0,0 +1,879 @@ +From 6825a35213d604a7149265af2346a69143c0853b Mon Sep 17 00:00:00 2001 +From: Benedikt Peetz <benedikt.peetz@b-peetz.de> +Date: Tue, 4 Mar 2025 19:15:06 +0100 +Subject: [PATCH] crates/*: Use the platform CA bundle instead of the + bundled certificates + +--- + Cargo.lock | 284 ++++++++++++++++++++++++++++++- + crates/cli/Cargo.toml | 2 +- + crates/common/Cargo.toml | 4 +- + crates/directory/Cargo.toml | 4 +- + crates/imap/Cargo.toml | 2 +- + crates/jmap/Cargo.toml | 4 +- + crates/mail-send/Cargo.toml | 1 + + crates/mail-send/src/smtp/tls.rs | 22 +-- + crates/managesieve/Cargo.toml | 2 +- + crates/pop3/Cargo.toml | 2 +- + crates/smtp/Cargo.toml | 4 +- + crates/spam-filter/Cargo.toml | 4 +- + crates/store/Cargo.toml | 2 +- + crates/trc/Cargo.toml | 2 +- + crates/utils/Cargo.toml | 5 +- + crates/utils/src/lib.rs | 16 +- + tests/Cargo.toml | 10 +- + 17 files changed, 314 insertions(+), 56 deletions(-) + +diff --git a/Cargo.lock b/Cargo.lock +index be36759b..eca9699f 100644 +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -440,6 +440,47 @@ dependencies = [ + "url", + ] + ++[[package]] ++name = "aws-lc-fips-sys" ++version = "0.13.3" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "29003a681b2b9465c1139bfb726da452a841a8b025f35953f3bce71139f10b21" ++dependencies = [ ++ "bindgen 0.69.5", ++ "cc", ++ "cmake", ++ "dunce", ++ "fs_extra", ++ "paste", ++ "regex", ++] ++ ++[[package]] ++name = "aws-lc-rs" ++version = "1.12.5" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "5e4e8200b9a4a5801a769d50eeabc05670fec7e959a8cb7a63a93e4e519942ae" ++dependencies = [ ++ "aws-lc-fips-sys", ++ "aws-lc-sys", ++ "paste", ++ "zeroize", ++] ++ ++[[package]] ++name = "aws-lc-sys" ++version = "0.26.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "0f9dd2e03ee80ca2822dd6ea431163d2ef259f2066a4d6ccaca6d9dcb386aa43" ++dependencies = [ ++ "bindgen 0.69.5", ++ "cc", ++ "cmake", ++ "dunce", ++ "fs_extra", ++ "paste", ++] ++ + [[package]] + name = "aws-region" + version = "0.25.5" +@@ -673,12 +714,15 @@ dependencies = [ + "itertools 0.12.1", + "lazy_static", + "lazycell", ++ "log", ++ "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn 2.0.96", ++ "which", + ] + + [[package]] +@@ -1035,6 +1079,12 @@ dependencies = [ + "smallvec", + ] + ++[[package]] ++name = "cesu8" ++version = "1.1.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" ++ + [[package]] + name = "cexpr" + version = "0.6.0" +@@ -1347,6 +1397,16 @@ dependencies = [ + "libc", + ] + ++[[package]] ++name = "core-foundation" ++version = "0.10.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" ++dependencies = [ ++ "core-foundation-sys", ++ "libc", ++] ++ + [[package]] + name = "core-foundation-sys" + version = "0.8.7" +@@ -1912,6 +1972,12 @@ dependencies = [ + "zeroize", + ] + ++[[package]] ++name = "dunce" ++version = "1.0.5" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" ++ + [[package]] + name = "dyn-clone" + version = "1.0.17" +@@ -2117,6 +2183,29 @@ dependencies = [ + "syn 2.0.96", + ] + ++[[package]] ++name = "env_filter" ++version = "0.1.3" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" ++dependencies = [ ++ "log", ++ "regex", ++] ++ ++[[package]] ++name = "env_logger" ++version = "0.11.6" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" ++dependencies = [ ++ "anstream", ++ "anstyle", ++ "env_filter", ++ "humantime", ++ "log", ++] ++ + [[package]] + name = "equivalent" + version = "1.0.1" +@@ -2423,6 +2512,12 @@ dependencies = [ + "syn 2.0.96", + ] + ++[[package]] ++name = "fs_extra" ++version = "1.3.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" ++ + [[package]] + name = "funty" + version = "2.0.0" +@@ -2974,6 +3069,12 @@ version = "0.4.3" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "9994b79e8c1a39b3166c63ae7823bb2b00831e2a96a31399c50fe69df408eaeb" + ++[[package]] ++name = "humantime" ++version = "2.1.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" ++ + [[package]] + name = "hyper" + version = "0.14.32" +@@ -3044,6 +3145,7 @@ dependencies = [ + "hyper 1.6.0", + "hyper-util", + "rustls 0.23.21", ++ "rustls-native-certs 0.8.1", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.1", +@@ -3607,6 +3709,28 @@ dependencies = [ + "utils", + ] + ++[[package]] ++name = "jni" ++version = "0.21.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" ++dependencies = [ ++ "cesu8", ++ "cfg-if", ++ "combine", ++ "jni-sys", ++ "log", ++ "thiserror 1.0.69", ++ "walkdir", ++ "windows-sys 0.45.0", ++] ++ ++[[package]] ++name = "jni-sys" ++version = "0.3.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" ++ + [[package]] + name = "jobserver" + version = "0.1.32" +@@ -3959,14 +4083,18 @@ dependencies = [ + [[package]] + name = "mail-send" + version = "0.5.0" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "b12277cdcacfc15af67fe9cf155f31ff68ad8c301304573ea116ed8870f192d5" + dependencies = [ + "base64 0.22.1", ++ "env_logger", + "gethostname", ++ "mail-auth", ++ "mail-builder", ++ "mail-parser", + "md5", ++ "rand 0.8.5", + "rustls 0.23.21", + "rustls-pki-types", ++ "rustls-platform-verifier", + "smtp-proto", + "tokio", + "tokio-rustls 0.26.1", +@@ -5552,6 +5680,7 @@ dependencies = [ + "pin-project-lite", + "quinn", + "rustls 0.23.21", ++ "rustls-native-certs 0.8.1", + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "serde", +@@ -5920,6 +6049,8 @@ version = "0.23.21" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" + dependencies = [ ++ "aws-lc-rs", ++ "log", + "once_cell", + "ring 0.17.8", + "rustls-pki-types", +@@ -5937,7 +6068,7 @@ dependencies = [ + "openssl-probe", + "rustls-pemfile 1.0.4", + "schannel", +- "security-framework", ++ "security-framework 2.11.1", + ] + + [[package]] +@@ -5950,7 +6081,19 @@ dependencies = [ + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "schannel", +- "security-framework", ++ "security-framework 2.11.1", ++] ++ ++[[package]] ++name = "rustls-native-certs" ++version = "0.8.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" ++dependencies = [ ++ "openssl-probe", ++ "rustls-pki-types", ++ "schannel", ++ "security-framework 3.2.0", + ] + + [[package]] +@@ -5980,6 +6123,33 @@ dependencies = [ + "web-time", + ] + ++[[package]] ++name = "rustls-platform-verifier" ++version = "0.5.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "e012c45844a1790332c9386ed4ca3a06def221092eda277e6f079728f8ea99da" ++dependencies = [ ++ "core-foundation 0.10.0", ++ "core-foundation-sys", ++ "jni", ++ "log", ++ "once_cell", ++ "rustls 0.23.21", ++ "rustls-native-certs 0.8.1", ++ "rustls-platform-verifier-android", ++ "rustls-webpki 0.102.8", ++ "security-framework 3.2.0", ++ "security-framework-sys", ++ "webpki-root-certs", ++ "windows-sys 0.52.0", ++] ++ ++[[package]] ++name = "rustls-platform-verifier-android" ++version = "0.1.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" ++ + [[package]] + name = "rustls-webpki" + version = "0.101.7" +@@ -5996,6 +6166,7 @@ version = "0.102.8" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" + dependencies = [ ++ "aws-lc-rs", + "ring 0.17.8", + "rustls-pki-types", + "untrusted 0.9.0", +@@ -6125,7 +6296,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" + dependencies = [ + "bitflags 2.8.0", +- "core-foundation", ++ "core-foundation 0.9.4", ++ "core-foundation-sys", ++ "libc", ++ "security-framework-sys", ++] ++ ++[[package]] ++name = "security-framework" ++version = "3.2.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" ++dependencies = [ ++ "bitflags 2.8.0", ++ "core-foundation 0.10.0", + "core-foundation-sys", + "libc", + "security-framework-sys", +@@ -6817,7 +7001,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" + dependencies = [ + "bitflags 1.3.2", +- "core-foundation", ++ "core-foundation 0.9.4", + "system-configuration-sys", + ] + +@@ -7569,6 +7753,7 @@ dependencies = [ + "rustls 0.23.21", + "rustls-pemfile 2.2.0", + "rustls-pki-types", ++ "rustls-platform-verifier", + "serde", + "serde_json", + "smtp-proto", +@@ -7764,6 +7949,15 @@ dependencies = [ + "untrusted 0.9.0", + ] + ++[[package]] ++name = "webpki-root-certs" ++version = "0.26.8" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "09aed61f5e8d2c18344b3faa33a4c837855fe56642757754775548fee21386c4" ++dependencies = [ ++ "rustls-pki-types", ++] ++ + [[package]] + name = "webpki-roots" + version = "0.25.4" +@@ -7789,6 +7983,18 @@ dependencies = [ + "once_cell", + ] + ++[[package]] ++name = "which" ++version = "4.4.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" ++dependencies = [ ++ "either", ++ "home", ++ "once_cell", ++ "rustix", ++] ++ + [[package]] + name = "whoami" + version = "1.5.2" +@@ -7886,6 +8092,15 @@ dependencies = [ + "windows-targets 0.52.6", + ] + ++[[package]] ++name = "windows-sys" ++version = "0.45.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" ++dependencies = [ ++ "windows-targets 0.42.2", ++] ++ + [[package]] + name = "windows-sys" + version = "0.48.0" +@@ -7913,6 +8128,21 @@ dependencies = [ + "windows-targets 0.52.6", + ] + ++[[package]] ++name = "windows-targets" ++version = "0.42.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" ++dependencies = [ ++ "windows_aarch64_gnullvm 0.42.2", ++ "windows_aarch64_msvc 0.42.2", ++ "windows_i686_gnu 0.42.2", ++ "windows_i686_msvc 0.42.2", ++ "windows_x86_64_gnu 0.42.2", ++ "windows_x86_64_gnullvm 0.42.2", ++ "windows_x86_64_msvc 0.42.2", ++] ++ + [[package]] + name = "windows-targets" + version = "0.48.5" +@@ -7944,6 +8174,12 @@ dependencies = [ + "windows_x86_64_msvc 0.52.6", + ] + ++[[package]] ++name = "windows_aarch64_gnullvm" ++version = "0.42.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" ++ + [[package]] + name = "windows_aarch64_gnullvm" + version = "0.48.5" +@@ -7956,6 +8192,12 @@ version = "0.52.6" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + ++[[package]] ++name = "windows_aarch64_msvc" ++version = "0.42.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" ++ + [[package]] + name = "windows_aarch64_msvc" + version = "0.48.5" +@@ -7968,6 +8210,12 @@ version = "0.52.6" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + ++[[package]] ++name = "windows_i686_gnu" ++version = "0.42.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" ++ + [[package]] + name = "windows_i686_gnu" + version = "0.48.5" +@@ -7986,6 +8234,12 @@ version = "0.52.6" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + ++[[package]] ++name = "windows_i686_msvc" ++version = "0.42.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" ++ + [[package]] + name = "windows_i686_msvc" + version = "0.48.5" +@@ -7998,6 +8252,12 @@ version = "0.52.6" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + ++[[package]] ++name = "windows_x86_64_gnu" ++version = "0.42.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" ++ + [[package]] + name = "windows_x86_64_gnu" + version = "0.48.5" +@@ -8010,6 +8270,12 @@ version = "0.52.6" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + ++[[package]] ++name = "windows_x86_64_gnullvm" ++version = "0.42.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" ++ + [[package]] + name = "windows_x86_64_gnullvm" + version = "0.48.5" +@@ -8022,6 +8288,12 @@ version = "0.52.6" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + ++[[package]] ++name = "windows_x86_64_msvc" ++version = "0.42.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" ++ + [[package]] + name = "windows_x86_64_msvc" + version = "0.48.5" +diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml +index a2d19a06..76866b80 100644 +--- a/crates/cli/Cargo.toml ++++ b/crates/cli/Cargo.toml +@@ -13,7 +13,7 @@ resolver = "2" + [dependencies] + jmap-client = { version = "0.3", features = ["async"] } + mail-parser = { version = "0.10", features = ["full_encoding", "serde_support"] } +-reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-webpki-roots", "http2"]} ++reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots", "http2"]} + tokio = { version = "1.23", features = ["full"] } + num_cpus = "1.13.1" + clap = { version = "4.1.6", features = ["derive"] } +diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml +index 3da0183f..93c49bb5 100644 +--- a/crates/common/Cargo.toml ++++ b/crates/common/Cargo.toml +@@ -16,7 +16,7 @@ sieve-rs = { version = "0.6" } + mail-parser = { version = "0.10", features = ["full_encoding"] } + mail-builder = { version = "0.4" } + mail-auth = { version = "0.6" } +-mail-send = { version = "0.5", default-features = false, features = ["cram-md5", "ring", "tls12"] } ++mail-send = { path = "../mail-send", default-features = false, features = ["cram-md5", "ring", "tls12"] } + smtp-proto = { version = "0.1", features = ["serde_support"] } + dns-update = { version = "0.1" } + ahash = { version = "0.8.2", features = ["serde"] } +@@ -32,7 +32,7 @@ tokio = { version = "1.23", features = ["net", "macros"] } + tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "tls12"] } + futures = "0.3" + rcgen = "0.12" +-reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-webpki-roots", "http2", "stream"]} ++reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots", "http2", "stream"]} + serde = { version = "1.0", features = ["derive"]} + serde_json = "1.0" + base64 = "0.22" +diff --git a/crates/directory/Cargo.toml b/crates/directory/Cargo.toml +index dc022e7a..10e0c00a 100644 +--- a/crates/directory/Cargo.toml ++++ b/crates/directory/Cargo.toml +@@ -12,7 +12,7 @@ trc = { path = "../trc" } + jmap_proto = { path = "../jmap-proto" } + smtp-proto = { version = "0.1" } + mail-parser = { version = "0.10", features = ["full_encoding", "serde_support"] } +-mail-send = { version = "0.5", default-features = false, features = ["cram-md5", "ring", "tls12"] } ++mail-send = { path = "../mail-send", default-features = false, features = ["cram-md5", "ring", "tls12"] } + mail-builder = { version = "0.4" } + tokio = { version = "1.23", features = ["net"] } + tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "tls12"] } +@@ -34,7 +34,7 @@ futures = "0.3" + regex = "1.7.0" + serde = { version = "1.0", features = ["derive"]} + totp-rs = { version = "5.5.1", features = ["otpauth"] } +-reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-webpki-roots", "http2"] } ++reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots", "http2"] } + serde_json = "1.0" + base64 = "0.22" + +diff --git a/crates/imap/Cargo.toml b/crates/imap/Cargo.toml +index 640ca4fd..d91931c1 100644 +--- a/crates/imap/Cargo.toml ++++ b/crates/imap/Cargo.toml +@@ -16,7 +16,7 @@ email = { path = "../email" } + nlp = { path = "../nlp" } + utils = { path = "../utils" } + mail-parser = { version = "0.10", features = ["full_encoding"] } +-mail-send = { version = "0.5", default-features = false, features = ["cram-md5", "ring", "tls12"] } ++mail-send = { path = "../mail-send", default-features = false, features = ["cram-md5", "ring", "tls12"] } + rustls = { version = "0.23.5", default-features = false, features = ["std", "ring", "tls12"] } + rustls-pemfile = "2.0" + tokio = { version = "1.23", features = ["full"] } +diff --git a/crates/jmap/Cargo.toml b/crates/jmap/Cargo.toml +index 7be56e44..ad5ed795 100644 +--- a/crates/jmap/Cargo.toml ++++ b/crates/jmap/Cargo.toml +@@ -18,7 +18,7 @@ email = { path = "../email" } + smtp-proto = { version = "0.1" } + mail-parser = { version = "0.10", features = ["full_encoding", "serde_support"] } + mail-builder = { version = "0.4" } +-mail-send = { version = "0.5", default-features = false, features = ["cram-md5", "ring", "tls12"] } ++mail-send = { path = "../mail-send", default-features = false, features = ["cram-md5", "ring", "tls12"] } + mail-auth = { version = "0.6", features = ["generate"] } + sieve-rs = { version = "0.6" } + serde = { version = "1.0", features = ["derive"]} +@@ -38,7 +38,7 @@ p256 = { version = "0.13", features = ["ecdh"] } + hkdf = "0.12.3" + sha1 = "0.10" + sha2 = "0.10" +-reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-webpki-roots", "http2"]} ++reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots", "http2"]} + tokio-tungstenite = "0.26" + tungstenite = "0.26" + chrono = "0.4" +diff --git a/crates/mail-send/Cargo.toml b/crates/mail-send/Cargo.toml +index fb5f402d..6760afab 100644 +--- a/crates/mail-send/Cargo.toml ++++ b/crates/mail-send/Cargo.toml +@@ -27,6 +27,7 @@ rustls = { version = "0.23", default-features = false, features = ["std"]} + tokio-rustls = { version = "0.26", default-features = false } + webpki-roots = { version = "0.26"} + rustls-pki-types = { version = "1" } ++rustls-platform-verifier = "0.5" + gethostname = { version = "0.5"} + + [dev-dependencies] +diff --git a/crates/mail-send/src/smtp/tls.rs b/crates/mail-send/src/smtp/tls.rs +index b15a6db8..7ddd0798 100644 +--- a/crates/mail-send/src/smtp/tls.rs ++++ b/crates/mail-send/src/smtp/tls.rs +@@ -12,9 +12,9 @@ use std::{convert::TryFrom, io, sync::Arc}; + + use rustls::{ + client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, +- ClientConfig, ClientConnection, RootCertStore, SignatureScheme, ++ ClientConfig, ClientConnection, SignatureScheme, + }; +-use rustls_pki_types::{ServerName, TrustAnchor}; ++use rustls_pki_types::ServerName; + use tokio::net::TcpStream; + use tokio_rustls::{client::TlsStream, TlsConnector}; + +@@ -78,20 +78,14 @@ impl SmtpClient<TlsStream<TcpStream>> { + } + + pub fn build_tls_connector(allow_invalid_certs: bool) -> TlsConnector { ++ use rustls_platform_verifier::BuilderVerifierExt; ++ ++ let config = ClientConfig::builder(); ++ + let config = if !allow_invalid_certs { +- let mut root_cert_store = RootCertStore::empty(); +- +- root_cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| TrustAnchor { +- subject: ta.subject.clone(), +- subject_public_key_info: ta.subject_public_key_info.clone(), +- name_constraints: ta.name_constraints.clone(), +- })); +- +- ClientConfig::builder() +- .with_root_certificates(root_cert_store) +- .with_no_client_auth() ++ config.with_platform_verifier().with_no_client_auth() + } else { +- ClientConfig::builder() ++ config + .dangerous() + .with_custom_certificate_verifier(Arc::new(DummyVerifier {})) + .with_no_client_auth() +diff --git a/crates/managesieve/Cargo.toml b/crates/managesieve/Cargo.toml +index 650ab23b..42738e68 100644 +--- a/crates/managesieve/Cargo.toml ++++ b/crates/managesieve/Cargo.toml +@@ -15,7 +15,7 @@ store = { path = "../store" } + utils = { path = "../utils" } + trc = { path = "../trc" } + mail-parser = { version = "0.10", features = ["full_encoding"] } +-mail-send = { version = "0.5", default-features = false, features = ["cram-md5", "ring", "tls12"] } ++mail-send = { path = "../mail-send", default-features = false, features = ["cram-md5", "ring", "tls12"] } + sieve-rs = { version = "0.6" } + rustls = { version = "0.23.5", default-features = false, features = ["std", "ring", "tls12"] } + rustls-pemfile = "2.0" +diff --git a/crates/pop3/Cargo.toml b/crates/pop3/Cargo.toml +index 5f86ed00..89e7b732 100644 +--- a/crates/pop3/Cargo.toml ++++ b/crates/pop3/Cargo.toml +@@ -15,7 +15,7 @@ trc = { path = "../trc" } + jmap_proto = { path = "../jmap-proto" } + email = { path = "../email" } + mail-parser = { version = "0.10", features = ["full_encoding"] } +-mail-send = { version = "0.5", default-features = false, features = ["cram-md5", "ring", "tls12"] } ++mail-send = { path = "../mail-send", default-features = false, features = ["cram-md5", "ring", "tls12"] } + rustls = { version = "0.23.5", default-features = false, features = ["std", "ring", "tls12"] } + tokio = { version = "1.23", features = ["full"] } + tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "tls12"] } +diff --git a/crates/smtp/Cargo.toml b/crates/smtp/Cargo.toml +index 5997c1c3..5f5badc2 100644 +--- a/crates/smtp/Cargo.toml ++++ b/crates/smtp/Cargo.toml +@@ -21,7 +21,7 @@ email = { path = "../email" } + spam-filter = { path = "../spam-filter" } + trc = { path = "../trc" } + mail-auth = { version = "0.6" } +-mail-send = { version = "0.5", default-features = false, features = ["cram-md5", "ring", "tls12"] } ++mail-send = { path = "../mail-send", default-features = false, features = ["cram-md5", "ring", "tls12"] } + mail-parser = { version = "0.10", features = ["full_encoding"] } + mail-builder = { version = "0.4" } + smtp-proto = { version = "0.1", features = ["serde_support"] } +@@ -47,7 +47,7 @@ blake3 = "1.3" + lru-cache = "0.1.2" + rand = "0.8.5" + x509-parser = "0.16.0" +-reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-webpki-roots", "http2"] } ++reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots", "http2"] } + serde = { version = "1.0", features = ["derive", "rc"] } + serde_json = "1.0" + num_cpus = "1.15.0" +diff --git a/crates/spam-filter/Cargo.toml b/crates/spam-filter/Cargo.toml +index f5b63353..c9176cf6 100644 +--- a/crates/spam-filter/Cargo.toml ++++ b/crates/spam-filter/Cargo.toml +@@ -14,12 +14,12 @@ smtp-proto = { version = "0.1", features = ["serde_support"] } + mail-parser = { version = "0.10", features = ["full_encoding"] } + mail-builder = { version = "0.4" } + mail-auth = { version = "0.6" } +-mail-send = { version = "0.5", default-features = false, features = ["cram-md5", "ring", "tls12"] } ++mail-send = { path = "../mail-send", default-features = false, features = ["cram-md5", "ring", "tls12"] } + tokio = { version = "1.23", features = ["net", "macros"] } + psl = "2" + hyper = { version = "1.0.1", features = ["server", "http1", "http2"] } + idna = "1.0" +-reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-webpki-roots", "http2", "stream"]} ++reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots", "http2", "stream"]} + decancer = "3.0.1" + unicode-security = "0.1.0" + infer = "0.16" +diff --git a/crates/store/Cargo.toml b/crates/store/Cargo.toml +index b0cf7d77..67c2d742 100644 +--- a/crates/store/Cargo.toml ++++ b/crates/store/Cargo.toml +@@ -15,7 +15,7 @@ rust-s3 = { version = "=0.35.0-alpha.2", default-features = false, features = [" + azure_core = { version = "0.21.0", optional = true } + azure_storage = { version = "0.21.0", default-features = false, features = ["enable_reqwest_rustls", "hmac_rust"], optional = true } + azure_storage_blobs = { version = "0.21.0", default-features = false, features = ["enable_reqwest_rustls", "hmac_rust"], optional = true } +-reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-webpki-roots", "http2", "stream"]} ++reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots", "http2", "stream"]} + tokio = { version = "1.23", features = ["sync", "fs", "io-util"] } + r2d2 = { version = "0.8.10", optional = true } + futures = { version = "0.3", optional = true } +diff --git a/crates/trc/Cargo.toml b/crates/trc/Cargo.toml +index e4f2ca7c..f294e469 100644 +--- a/crates/trc/Cargo.toml ++++ b/crates/trc/Cargo.toml +@@ -11,7 +11,7 @@ mail-parser = { version = "0.10", features = ["full_encoding"] } + base64 = "0.22.1" + serde = "1.0" + serde_json = "1.0.120" +-reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-webpki-roots", "http2"]} ++reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots", "http2"]} + bincode = "1.3.3" + rtrb = "0.3.1" + parking_lot = "0.12.3" +diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml +index e0a7ef9d..14b1d675 100644 +--- a/crates/utils/Cargo.toml ++++ b/crates/utils/Cargo.toml +@@ -9,12 +9,13 @@ trc = { path = "../trc" } + rustls = { version = "0.23.5", default-features = false, features = ["std", "ring", "tls12"] } + rustls-pemfile = "2.0" + rustls-pki-types = { version = "1" } ++rustls-platform-verifier = "0.5" + tokio = { version = "1.23", features = ["net", "macros"] } + tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "tls12"] } + serde = { version = "1.0", features = ["derive"]} + mail-auth = { version = "0.6" } + smtp-proto = { version = "0.1" } +-mail-send = { version = "0.5", default-features = false, features = ["cram-md5", "ring", "tls12"] } ++mail-send = { path = "../mail-send", default-features = false, features = ["cram-md5", "ring", "tls12"] } + ahash = { version = "0.8" } + chrono = "0.4" + rand = "0.8.5" +@@ -23,7 +24,7 @@ ring = { version = "0.17" } + base64 = "0.22" + serde_json = "1.0" + rcgen = "0.13" +-reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-webpki-roots", "http2", "stream"]} ++reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots", "http2", "stream"]} + x509-parser = "0.16.0" + pem = "3.0" + parking_lot = "0.12" +diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs +index acec2f04..b2cdaf65 100644 +--- a/crates/utils/src/lib.rs ++++ b/crates/utils/src/lib.rs +@@ -18,9 +18,9 @@ use futures::StreamExt; + use reqwest::Response; + use rustls::{ + client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, +- ClientConfig, RootCertStore, SignatureScheme, ++ ClientConfig, SignatureScheme, + }; +-use rustls_pki_types::TrustAnchor; ++use rustls_platform_verifier::BuilderVerifierExt; + + pub const BLOB_HASH_LEN: usize = 32; + +@@ -280,17 +280,7 @@ pub fn rustls_client_config(allow_invalid_certs: bool) -> ClientConfig { + let config = ClientConfig::builder(); + + if !allow_invalid_certs { +- let mut root_cert_store = RootCertStore::empty(); +- +- root_cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| TrustAnchor { +- subject: ta.subject.clone(), +- subject_public_key_info: ta.subject_public_key_info.clone(), +- name_constraints: ta.name_constraints.clone(), +- })); +- +- config +- .with_root_certificates(root_cert_store) +- .with_no_client_auth() ++ config.with_platform_verifier().with_no_client_auth() + } else { + config + .dangerous() +diff --git a/tests/Cargo.toml b/tests/Cargo.toml +index 6aa6d35b..256a574b 100644 +--- a/tests/Cargo.toml ++++ b/tests/Cargo.toml +@@ -34,12 +34,12 @@ spam-filter = { path = "../crates/spam-filter", features = ["test_mode", "enterp + trc = { path = "../crates/trc" } + managesieve = { path = "../crates/managesieve", features = ["test_mode", "enterprise"] } + smtp-proto = { version = "0.1" } +-mail-send = { version = "0.5", default-features = false, features = ["cram-md5", "ring", "tls12"] } ++mail-send = { path = "../crates/mail-send", default-features = false, features = ["cram-md5", "ring", "tls12"] } + mail-auth = { version = "0.6", features = ["test"] } +-sieve-rs = { version = "0.6" } ++sieve-rs = { version = "0.6" } + utils = { path = "../crates/utils", features = ["test_mode"] } +-jmap-client = { version = "0.3", features = ["websockets", "debug", "async"] } +-mail-parser = { version = "0.10", features = ["full_encoding", "serde_support"] } ++jmap-client = { version = "0.3", features = ["websockets", "debug", "async"] } ++mail-parser = { version = "0.10", features = ["full_encoding", "serde_support"] } + tokio = { version = "1.23", features = ["full"] } + tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "tls12"] } + rustls = { version = "0.23.5", default-features = false, features = ["std", "ring", "tls12"] } +@@ -50,7 +50,7 @@ rayon = { version = "1.5.1" } + flate2 = { version = "1.0.17", features = ["zlib"], default-features = false } + serde = { version = "1.0", features = ["derive"]} + serde_json = "1.0" +-reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-webpki-roots", "multipart", "http2"]} ++reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots", "multipart", "http2"]} + bytes = "1.4.0" + futures = "0.3" + ece = "2.2" +-- +2.47.2 + diff --git a/pkgs/by-name/st/stalwart-mail-free/patches/crates-main-Cargo.toml-Use-libre-features.patch b/pkgs/by-name/st/stalwart-mail-free/patches/crates-main-Cargo.toml-Use-libre-features.patch new file mode 100644 index 0000000..5844793 --- /dev/null +++ b/pkgs/by-name/st/stalwart-mail-free/patches/crates-main-Cargo.toml-Use-libre-features.patch @@ -0,0 +1,25 @@ +From 763d22e53a9c99addbd7f9ca84f0083733f3d10d Mon Sep 17 00:00:00 2001 +From: Benedikt Peetz <benedikt.peetz@b-peetz.de> +Date: Tue, 4 Mar 2025 19:00:15 +0100 +Subject: [PATCH] crates/main/Cargo.toml: Use libre features + +--- + crates/main/Cargo.toml | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/crates/main/Cargo.toml b/crates/main/Cargo.toml +index 036d2dcf..709ae5da 100644 +--- a/crates/main/Cargo.toml ++++ b/crates/main/Cargo.toml +@@ -35,7 +35,7 @@ jemallocator = "0.5.0" + + [features] + #default = ["sqlite", "postgres", "mysql", "rocks", "elastic", "s3", "redis", "azure", "enterprise"] +-default = ["rocks", "enterprise"] ++default = ["rocks", "redis"] + sqlite = ["store/sqlite"] + foundationdb = ["store/foundation", "common/foundation"] + postgres = ["store/postgres"] +-- +2.47.2 + diff --git a/pkgs/by-name/st/stalwart-mail-free/spam-filter.nix b/pkgs/by-name/st/stalwart-mail-free/spam-filter.nix new file mode 100644 index 0000000..ce3466d --- /dev/null +++ b/pkgs/by-name/st/stalwart-mail-free/spam-filter.nix @@ -0,0 +1,24 @@ +{ + stdenv, + fetchFromGitHub, +}: +stdenv.mkDerivation (finalAttrs: { + pname = "spam-filter"; + version = "2.0.2"; + + src = fetchFromGitHub { + owner = "stalwartlabs"; + repo = "spam-filter"; + tag = "v${finalAttrs.version}"; + hash = "sha256-p2F0bwYBNWeoHjp9csWWaqeOISk9dNQT28OqkhUr7ew="; + }; + + buildPhase = '' + bash ./build.sh + ''; + + installPhase = '' + mkdir --parents "$out" + cp ./spam-filter.toml "$out/" + ''; +}) diff --git a/pkgs/default.nix b/pkgs/default.nix index e665eb2..5456ad4 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -1,6 +1,7 @@ { pkgs, nixLib, + pkgsUnstable, }: let inherit (pkgs) lib; @@ -10,7 +11,7 @@ lib.callPackageWith (nixLib.warnMerge pkgs - vhackPkgs + (nixLib.warnMerge vhackPkgs {inherit nixLib pkgsUnstable;} wMM) wMM); vhackPkgs = nixLib.mkByName { diff --git a/scripts/get_dns.sh b/scripts/get_dns.sh new file mode 100755 index 0000000..2d82925 --- /dev/null +++ b/scripts/get_dns.sh @@ -0,0 +1,55 @@ +#! /usr/bin/env nix-shell +#! nix-shell -p dig -p dash -i dash --impure +# shellcheck shell=dash + +get_dns_types() { + cat <<EOF + A + AAAA + CAA + CNAME + DNAME + MX + NS + SOA + SRV + TXT + PTR + DNSKEY + DS + SSHFP + TLSA + OPENPGPKEY + SVCB + HTTPS +EOF +} + +check_type() { + domain="$1" + type="$2" + + if [ "$(dig +short -t "$type" "$domain" | wc -c)" -ne 0 ]; then + dig +short -t "$type" "$domain" | while IFS="$(printf "\n")" read -r output; do + printf "(%s) %s [%s]\n" "$type" "$output" "$domain" + done + else + printf "(%s) <Not set> [%s]\n" "$type" "$domain" + fi +} + +get_dns() { + original_domain="$1" + + get_dns_types | while read -r type; do + check_type "$original_domain" "$type" + done + + # DKIM + check_type "mail._domainkey.$original_domain" "TXT" + + # DMARC + check_type "_dmarc.$original_domain" "TXT" +} + +get_dns "$1" diff --git a/scripts/system_info.sh b/scripts/system_info.sh new file mode 100755 index 0000000..940406a --- /dev/null +++ b/scripts/system_info.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env sh + +# Take a host name and return the nix store path to the host's system info. +# Type +# _system_info :: String -> Path +_system_info() { + nix --option warn-dirty false build .#nixosConfigurations."$1".config.vhack.system-info.markdown --print-out-paths --no-link +} + +_glow() { + if command -v glow >/dev/null; then + glow --width 0 + else + cat + fi +} + +# The expression is not meant to be expanded by the shell +# shellcheck disable=SC2016 +nix eval --expr '"${builtins.concatStringsSep "\n" (builtins.attrNames (builtins.fromTOML (builtins.readFile ./hosts/host-names.toml)))}\n"' --impure --raw | while read -r host; do + echo "# $host" | _glow + _glow <"$(_system_info "$host")" +done + +# vim: ft=sh diff --git a/scripts/test_build.sh b/scripts/test_build.sh new file mode 100755 index 0000000..eeb8572 --- /dev/null +++ b/scripts/test_build.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env sh + +[ "$#" -ne 2 ] && { + echo "Usage: test_build <TEST_TARGET> <BUILD_NODE>" 1>&2 + exit 2 +} + +test_target="$1" +build_node="$2" + +nix build .#checks.x86_64-linux."$test_target".nodes."$build_node".system.build.toplevel + +# vim: ft=sh diff --git a/scripts/test_interactive.sh b/scripts/test_interactive.sh index 3b3fe0d..230f5a0 100755 --- a/scripts/test_interactive.sh +++ b/scripts/test_interactive.sh @@ -8,7 +8,9 @@ test_target="$1" exit 1 } -nix build .#checks.x86_64-linux."$test_target".driver +nix build .#checks.x86_64-linux."$test_target".driverInteractive || { + exit 1 +} ./result/bin/nixos-test-driver --interactive diff --git a/secrets.nix b/secrets.nix index bd5630e..8d3ae92 100644 --- a/secrets.nix +++ b/secrets.nix @@ -2,24 +2,72 @@ let soispha = "age1mshh4ynzhhzhff25tqwkg4j054g3xwrfznh98ycchludj9wjj48qn2uffn"; sils = "age1vuhaey7kd9l76y6f9weeqmde3s4kjw38869ju6u3027yece2r3rqssjxst"; - server1 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMnqsfIZjelH7rcvFvnLR5zUZuC8thsBupBlvjcMRBUm"; + server2HostKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL1TUFoCTplkqTVbXQ6qDCyeo2h8+C0vjrIlKu6vmq5f"; + server3HostKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP3s4FjGx7LEVf/GE3WeCl8TmCtPt8gW1J0mp0fUJBNm"; - allSecrets = [ - soispha - sils - server1 - ]; -in { - "./modules/by-name/et/etesync/secret_file.age".publicKeys = allSecrets; - "./modules/by-name/pe/peertube/secrets/general.age".publicKeys = allSecrets; - "./modules/by-name/pe/peertube/secrets/smtp.age".publicKeys = allSecrets; + # WARNING(@bpeetz): ONLY use this key on age files that are meant to be public! <2025-02-23> + testingKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILxdvBk/PC9fC7B5vqe9TvygZKY6LgDQ2mXRdVrthBM/"; - "./system/secrets/backup/backuppass.age".publicKeys = allSecrets; - "./system/secrets/backup/backupssh.age".publicKeys = allSecrets; - "./system/secrets/invidious/hmac.age".publicKeys = allSecrets; - "./system/secrets/mastodon/mail.age".publicKeys = allSecrets; - "./system/secrets/matrix-synapse/passwd.age".publicKeys = allSecrets; - "./system/secrets/miniflux/admin.age".publicKeys = allSecrets; - "./system/secrets/taskserver/ca.age".publicKeys = allSecrets; - "./system/secrets/taskserver/systemd_tmpfiles.age".publicKeys = allSecrets; -} + publicKeys = { + "server2" = [ + soispha + sils + server2HostKey + ]; + + "server3" = [ + soispha + sils + server3HostKey + ]; + }; + + lock = builtins.fromJSON (builtins.readFile ./flake.lock); + nixLib = + import (builtins.fetchTree lock.nodes.library.locked).outPath {}; + inherit ((import (builtins.fetchTree lock.nodes.nixpkgs.locked).outPath {})) lib; + + secrets = let + base = nixLib.mkByName { + useShards = false; + fileName = "secrets"; + baseDirectory = ./hosts/by-name; + }; + secrets = builtins.mapAttrs (name: value: + nixLib.mkByName { + relativePaths = true; + useShards = false; + fileRegex = "^.*\.age$"; + baseDirectory = value; + }) + base; + allSecretPaths = builtins.mapAttrs (serverName: secrets: + lib.lists.flatten ( + lib.attrsets.mapAttrsToList + (service: fileNames: builtins.map (fileName: "./hosts/by-name/${serverName}/secrets/${service}/${fileName}") fileNames) + secrets + )) + secrets; + in + # We should be able to merge with the `//` operator here because all attribute paths + # must be unique (they were files previously) + builtins.foldl' (acc: elem: acc // elem) {} ( + builtins.attrValues (builtins.mapAttrs (serverName: secretPaths: + builtins.listToAttrs ( + builtins.map + (secretPath: { + name = secretPath; + value.publicKeys = publicKeys."${serverName}"; + }) + secretPaths + )) + allSecretPaths) + ); +in + secrets + // { + "./tests/by-name/em/email-dns/secrets/dkim/alice.com/private.age".publicKeys = [soispha sils testingKey]; + "./tests/by-name/em/email-dns/secrets/dkim/bob.com/private.age".publicKeys = [soispha sils testingKey]; + "./tests/by-name/em/email-dns/secrets/dkim/mail1.server.com/private.age".publicKeys = [soispha sils testingKey]; + "./tests/by-name/em/email-dns/secrets/dkim/mail2.server.com/private.age".publicKeys = [soispha sils testingKey]; + } diff --git a/system/default.nix b/system/default.nix deleted file mode 100644 index 9fdd937..0000000 --- a/system/default.nix +++ /dev/null @@ -1,7 +0,0 @@ -{...}: { - imports = [ - ./packages - ./secrets - ./services - ]; -} diff --git a/system/packages/default.nix b/system/packages/default.nix deleted file mode 100644 index 12e4c93..0000000 --- a/system/packages/default.nix +++ /dev/null @@ -1,10 +0,0 @@ -{pkgs, ...}: { - environment.systemPackages = with pkgs; [ - jre_minimal - git - zsh - neovim - btrfs-progs - ]; - programs.zsh.enable = true; -} diff --git a/system/secrets/backup/backuppass.age b/system/secrets/backup/backuppass.age deleted file mode 100644 index 1931226..0000000 --- a/system/secrets/backup/backuppass.age +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN AGE ENCRYPTED FILE----- -YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1TEVTcmRkbkVHUVhzMU9J -YmVadUJWTTloZDZ0S2l2bnViSEpESXQ0d2dVCmRQbUx0Smh6b0ZCL2ZOL1NrMk9a -Q21OZi9Wd2d1Y1AxWVJZdHpjTmp2dzQKLT4gWDI1NTE5IE1ZblNNSEJ6Z1ZOZFJh -Nm5zOUNiV3ppRkgwNlVGaUV4S1dsbUVQeEhlV0UKUDVCV0UyY1JPczcvTVdFa2hw -WkVQeGJrcXRBSnRaeE1EZEZRSHBPS1ExTQotPiBzc2gtZWQyNTUxOSBPRDhUNGcg -ejZDNEJhK0dXb084MG41ZmpHVDRTaEpzOFN3RXNkNHdJR0UvYXppUjVqQQoyRlZP -MExORHN6cGhUZlFHRTc5OGV2cW5NczhSUFdGcmxmeS9GYTFDSk9VCi0+IHFdIWdL -aThRLWdyZWFzZSA2JmIgfVo3dXt7ayBGPEVOXEggd08KWFdtVkxKN256QXhBTjBT -RTBEeTBSbjNtSGRYNllOYlJRVjN0Vk9XV2FQQ2hlSWVHT3RUelVKZ2ZGZ2k5OWlN -Ywp6RktpamU4TVVPdENTNzh2VFFtdGNIZFdDRUxFbEIwSFJ5RmdmbWxibVR3bE55 -M1B0eFJmSldCU1NMWW01ejUyCgotLS0gbS9vZmtpYjA5OThTcnIwZ01lYkFTK0xy -Q05zdExqdTJZeVlOM0xqVTUrRQoZdRtAQ8c/dfZeZ2k5YhdEyNYn0fZQP3vKnj7b -s1AB5m+oTMDIn05x05lDsvqcFCvxZZNPzmkj5KzWXVxvg4/5Wr06BVdZaTHsrPqu -SmXsQmF8uqB6xctT3+ne3PGopF8J ------END AGE ENCRYPTED FILE----- diff --git a/system/secrets/backup/backupssh.age b/system/secrets/backup/backupssh.age deleted file mode 100644 index ba04931..0000000 --- a/system/secrets/backup/backupssh.age +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN AGE ENCRYPTED FILE----- -YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVMW91eE40QVhCcXlCSktD -NkE5ZUVnWm56L0hxaU4wYzJ6dHFFd05Xa0dzCjJTeGNsL2RCdm0raHBQaVhQbVFJ -MWc4d1RNamhTSWJLaDJPdjZram11ZEkKLT4gWDI1NTE5IGFRdStWbVJSTFhNeWVG -dUVjbk9qdWJxNm0wenBWcmFieWtMQjFLQWU1aTAKSG94WlhGVEN6MHZiRFNKcTI4 -cFdqM0Q5eGxTRGFwVWhiR0pRS2NVZUs2dwotPiBzc2gtZWQyNTUxOSBPRDhUNGcg -dFFyUVFTOGttcUpOcE82YXM1Yk11VGJUUllsWXRQNitTeU9WMHhYSGJVawpwZXIz -SGNvTkNGM2lyMXMzeFNBZ0NacS94enRvUWpGU1Y5Vy9kRkhTOTBFCi0+IEpkLWdy -ZWFzZSA2aXgnICd5IE5jRGFQLyBLTFVxWWY8KApycVRUdFN5MVNtc2x3cEJMeWFi -R0QveWJmN3BWS2J3REpOekpLc1czNGE3OTZLMjlpS1VubUpOM1R0MWlkazNUClZP -K01KUzVZQjltcW9yUXpmTlZMTnVqQU5uYwotLS0gb2pIdnY1YmdXV3RnbXJnejVI -aGRaTnczUUxiK1gwSkpsMUJxNzRmVjFRUQosYNaZP/ahgrMf/vP+Bbsy6kis9Fwa -UwBVwDE7nqRcyoIrtodUtq3I2cDog6YGzmvqY1yJHsuumqo3K6sZJK+/oINn9vLf -O85CZAcnV58CRFnc4LHQoFCz/wa7/osajjz5dQYcfd201qP3qESIjqRkwALqDmL5 -9abcDdgpCvjYYKi2ULKgdMyeqHsd7cTIVLJm1U7iZC6EnmcGiIy+c/pmDuK0OMl0 -+3CVNiy4qHGkYvwKzkq1fewzwvKTQchZaXgNDa57cSOOgh4lX8gU8eRfKg6REKod -0jIaH2zN/UhOPkqpyf9Twi5vLk475RiLf+8cTNi/BL/ZtHf0xYfbcdJT4wPOUZNd -P11eVDDUYnFvbszPSmRA8bueEQv9SZrYnS/DG4yDjpLFP9LCPIjdTr5OPvIfTpUy -cu22C6VIii54kj7uztYd/0rqSrJ5mClPIDhTUJUeAdTI17NHbAM5BJUpDmXdaBAM -LPWwu5KaiYjq7FmB39Qhp2I4hgmrEl/dorlU30VaV+uuV741ftWIykQJ9tmZUF9J -nA2ygMaWq/xrXDQ6bMnR+gP8i9BiLXPk/ug= ------END AGE ENCRYPTED FILE----- diff --git a/system/secrets/default.nix b/system/secrets/default.nix deleted file mode 100644 index b74e883..0000000 --- a/system/secrets/default.nix +++ /dev/null @@ -1,54 +0,0 @@ -{...}: { - age = { - secrets = { - invidiousHmac = { - file = ./invidious/hmac.age; - mode = "700"; - owner = "root"; - group = "root"; - }; - mastodonMail = { - file = ./mastodon/mail.age; - mode = "700"; - owner = "mastodon"; - group = "mastodon"; - }; - matrix-synapse_registration_shared_secret = { - file = ./matrix-synapse/passwd.age; - mode = "700"; - owner = "matrix-synapse"; - group = "matrix-synapse"; - }; - minifluxAdmin = { - file = ./miniflux/admin.age; - mode = "700"; - owner = "root"; - group = "root"; - }; - resticpass = { - file = ./backup/backuppass.age; - mode = "0700"; - owner = "root"; - group = "root"; - }; - resticssh = { - file = ./backup/backupssh.age; - mode = "0700"; - owner = "root"; - group = "root"; - }; - taskserverCaKey = { - file = ./taskserver/ca.age; - mode = "700"; - owner = "root"; - group = "root"; - }; - taskserverSystemdTmpfiles = { - file = ./taskserver/systemd_tmpfiles.age; - mode = "700"; - owner = "root"; - group = "root"; - }; - }; - }; -} diff --git a/system/secrets/invidious/hmac.age b/system/secrets/invidious/hmac.age deleted file mode 100644 index f760fa9..0000000 --- a/system/secrets/invidious/hmac.age +++ /dev/null @@ -1,14 +0,0 @@ ------BEGIN AGE ENCRYPTED FILE----- -YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvZGJGNzVGUWhsVTJFUGds -dFZmVnRnY1NrVTZBWEt2eFp1YU4yM0xoOUgwClZZNDNFQlp2aEx1eHVqbE5ZU29t -dVpMcStrMXd5WEFOaDJUVlVuUnJ4YkkKLT4gWDI1NTE5IEZSTVFhdk83RGRNWWdZ -bmQyd0FNTWhrUUxSRjVOQjAvWSsyU1Z4OWFvVUUKdkIraVRtRW5mUnZFbVRkcDBw -ME5NTDVkRUo1b0d1Z2xERWZnS0tMLzFhYwotPiBzc2gtZWQyNTUxOSBPRDhUNGcg -d09jY1doam1nc3B3MEVqN0grM3JWZzFwMW5WU2ZYdGh0TUZnM0VVdzJBSQppL3Qv -T0VDOTc1U3gyaTB6YVV4dDhEVU1OMzdlMnV2dC9zMVl1VkdkRmlBCi0+IGc/SEJa -aDZoLWdyZWFzZSBKPW1xOFRaIE9DUCBdfl1HXVUKL0I4MTJZT1ljOXE3cUtTR0Fv -S3E2UHcvYWxhUlU5QkdXVWZyUjU0SlcveG9GcjZZV242QXVwaDBQTjN0VldBCi0t -LSB6S0E2SWtmaXBnRkI5aFNIOU9VWkdhOHQrQ0x0MzJ3TC9aNkpJSTY5eDkwClOc -N6wSpWFX87Vbr+J8Sxn9O6uRbYAyNDmiJk5mDqYaqy/+PRPTx0gbmqRz911sW5Zx -aBKfDzSPjNx0CSKKL7ioTYlRrW0YyQ== ------END AGE ENCRYPTED FILE----- diff --git a/system/secrets/mastodon/mail.age b/system/secrets/mastodon/mail.age deleted file mode 100644 index c64a2e7..0000000 --- a/system/secrets/mastodon/mail.age +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN AGE ENCRYPTED FILE----- -YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqT05Uc2hrcFAwd1c5S1o0 -L3hhQURmdUVBbmxSYVFGczdGWThTck9VdkhRCktOZ1JSamN0Ly9pVXJDMDZ4Y0VZ -bmRyMTlaOU9HOEZ5SitzOVovUkhCNFUKLT4gWDI1NTE5IHlqUTFtODd6QXpNMFBY -WTY2cTJ2TFI5S0ZGc1doeEVEUi9veGRDKzN5UWsKUC9WZUtXVUs5cnkxL3Y5RlJs -RTRkNE5zQ0NtbG0vdStuZXZVUzFoeTBwNAotPiBzc2gtZWQyNTUxOSBPRDhUNGcg -Um1qczl3YTM0S3dIb3AzQmpSNVNNUXFzMFNLNEEwQllOSUkrMHNzVy9uMApTdjhz -U250NGNpdk5SbWhPNjhjWWM0aWovRCt0MjR3M29JSTZjLy9IbTAwCi0+IEwtZ3Jl -YXNlIEp6KCk4by1jIF0Kd2xoKytCU3d3MGFxZmRmS2gxSDJiVFp1L3hOS2hJVEtz -NlFHWHhnRW5SNTZRMFFFRUJrVXo2blZvNlZTSXNqeQpVbWFLUmVHN1ptWGdLMkJT -RVJuUWxTVE4vcDhsCi0tLSA5ckxpdFhrQWErb2NkcXlWaHR6WmVndVppbjRIQ3cw -VjAxdTlnTEdmTkVrCou6/oezocFtYn7QDWLFzknFPlD5d1xBFutng6dvazWasZXD -qecouKvAmFFA4mQHUjbmD2QxWdorU7SyYpEPeTJ4rbOuayySkYPxUoo8gqvd7JkS -0VCavUuSb8nmfk24E3M= ------END AGE ENCRYPTED FILE----- diff --git a/system/secrets/matrix-synapse/passwd.age b/system/secrets/matrix-synapse/passwd.age deleted file mode 100644 index 232aeb6..0000000 --- a/system/secrets/matrix-synapse/passwd.age +++ /dev/null @@ -1,14 +0,0 @@ ------BEGIN AGE ENCRYPTED FILE----- -YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3a3dCQWVLSXRDbWFNcUZk -clgrTWJsVE5lckhFMlFVV2VHMWc4TzJncXdrCjRITFF3SHJKMG1XS3Z6aXNFQk51 -OFE1Yks1UFhBKzI1dXBoZ09pOGhRK0kKLT4gWDI1NTE5IEFESW5uZmpTdXMyMkY0 -R04yRi9zcENrZXZHM3FsNGdrajhHNEw3WGp1eGcKU1IvNWVQaWpvU0E0TE1jTGlK -R1FhWGwwMjBIK3ppUkFSUEc3NDZhS3dUOAotPiBzc2gtZWQyNTUxOSBPRDhUNGcg -aVhNazlUb1o1Qm9XR0Nhd2RWWU0rTEcyUTZsWWVURXUyMjdmYUZXS2ZWSQpnby9k -RFQxSDdJQUZHOE4xakV6OWhKa040QVdMUXhZRW9ONTd5elQrSHRnCi0+IEJAXn0x -RytjLWdyZWFzZQpNc1F0MkVYWVd5QW5Mc3hueElLQ2FtSVJ6aytMV25RZDZaMHhT -QQotLS0gUi9iUHE4cTNHa2luempIQ1JQWHloZVh3aVFZSlpkcVByc3dFeHIvdndJ -Ywp4gcNh224W56TKdznbWsSJv6J4Z2zQmJ2lNwbD73OPILsR4GDjwOYjw4N8MVaK -TaelbSw9GRS7vQ9ZIGvAek05seHU0iTRansZXONhhErHtozjuMqJB7vJTHBo/ZSp -61MK ------END AGE ENCRYPTED FILE----- diff --git a/system/secrets/miniflux/admin.age b/system/secrets/miniflux/admin.age deleted file mode 100644 index 6b34ab0..0000000 --- a/system/secrets/miniflux/admin.age +++ /dev/null @@ -1,14 +0,0 @@ ------BEGIN AGE ENCRYPTED FILE----- -YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0ZHJ3V0E3bjVLYUd5N2gx -eE15dlBldWt1ZGpBcGc3ZWcwMTNKSTcxR0Y0Cm03dEgxYzdhYjYvWFNNUVdtR3E1 -dW1lMlE3R3dlcUZ1Qm1GMElPQU8xYmMKLT4gWDI1NTE5IFJrc28wZzhWQ3RoeFFK -WFlTSmVzRGMzamxrQ0NSUG9KVWxSajJsQ1BablEKS0tFb096djZOdUJIVTdaSndH -b1ZMT3ZCZGVkaWMvU0hPSFhsMkY3RzBkNAotPiBzc2gtZWQyNTUxOSBPRDhUNGcg -SWdGV1pSYzY3bWxadWJZeXVmTXBHeGpMTTYyak1IbE9jTjZQS3dwRXozUQo1UFlT -am9WNzh1TytMNTFsNjM4amh0N2JDdkxjYk9GL285UWUrZHV5L3p3Ci0+IEkqMS1n -cmVhc2UgZV4KRFlYWlRyNDFtZlJWcS9vZ1hiUkJxdE9saHpTTWQ3TitMc1N0UXBE -eWZ5SQotLS0gRzE4bmpSTWpjUnlHUlNHTTNWSjNNL0d3VFFpVFdOaVlMUERmRHNt -d2k3WQqd+49pa75kfJffbdCOmNvPLUN7N+d+lI4lXlPTyLWTNnM8qaVz+BAhMH40 -ri9BTHHtg4ql7bXZWXZt/CiBLUOuv+yKckm4u51vjOwyHwUjaMYF4bfXS+rChsQV -BL+XWihQZ5wNsUh1PRHMy3mrF1XSYROa4ApK/i5Sgm271cvBMI4C4G+oux0/wvkL ------END AGE ENCRYPTED FILE----- diff --git a/system/secrets/taskserver/ca.age b/system/secrets/taskserver/ca.age deleted file mode 100644 index e5d596d..0000000 --- a/system/secrets/taskserver/ca.age +++ /dev/null @@ -1,448 +0,0 @@ ------BEGIN AGE ENCRYPTED FILE----- -YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBiUFlnZVNVQXlqNmpTdTM0 -UmFsQklFRkRCWmdGL3BJbDN3U1dIdkxqa2trCjgzY3lMM3ArTGJuNUJwQzg1eVJR -VDNXbkRldFpWeEFGZm9XeUF2cGhPQ2sKLT4gWDI1NTE5IEYrQm9yNW9xS3VTbjRH -d1Njc1c5NUhvZnRENGdRVVRhdlhvVTF1b0xEMzQKOGJtTUtDUHk4c3VGSVNRcmZw -MWZrdTd0bXFPdHQ4YndkNlAzdDd0a0tLWQotPiBzc2gtZWQyNTUxOSBPRDhUNGcg -cmgxMFJEOHk2MVVQV0VHTHdWYXB4cmYxcjdmaU15ZFRTL1RZMFZMTmhCWQp2YTFk -SWJhT3p1WXh0V3FGYVVGMjQ1blJ2RjRGcllGK0Zyc1JmWWdGWUtNCi0+IGxndilP -eEVeLWdyZWFzZSB4RyBpSSB5YTB5CmNUTHBtV3lqRmx4ZFppTzFHRTBHRk9yUnZs -bEJsbFQ4MHR6cFNuZUJUazgKLS0tIE5ibzhONnEwYnRnSmduRnZuUmJQM2FKS2RE -bjI4MGxrdTh0dm12YWNpeW8KuRqzoQ32IvJ7LZDP+LpknCtLVK0K+W5R4mJQnQh1 -kMS1+wed3lWrn3nlBbctxsBFmlpHpj8hEBfXwb+lH4mxxUXtgvqVrwp4IdxREJco -ehP7ZrpKKV4gLFkWfRhLZo+J6kIlzfU1hnVtU0s+6lQ78noPajjPkB2v5eIosYta -VIJ0mXEhJVIHnLYDx2BD/BBH8St+vvUsGM3Md+NWhPyN9LNTY7cUB2Ae5m8yfI3F -bEC21pWsLsOek7Xw88FctxOnljhROnRB1sCnEbttzelaDPu56LrEbOVjXl/k+OTm -TO1dWZze8oVwYulq7+o8CyvfjL0Q+8Knuf9pjk263Mg0DLs3la3vKCPB/iIw8l/K -NL29DTRi1FK49EL+RpLmPB+74TUa8WwaOsregUf3sEDNBxdOrwKukh0K1tZdZio7 -/t/zFdZn1zgmwNVUvty2aKX53H5/qVcspPlOpA5INrcu2JqCGQz3c8R6GXZcZH0O -AZxopR12DkbWhsCTgROxjD29PcGsxa4BZxDotatkAYOfpZcmOELWvkqYV/wgcy3i -gVOy2gUHxTy9nDItr2DzKbLUNCbDtzA9aUVbhIGkIuu1oW1S3M5h9CqC4C0NrMPv -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 -oCA7iMktvBx+T+eRflSqCahL ------END AGE ENCRYPTED FILE----- diff --git a/system/secrets/taskserver/systemd_tmpfiles.age b/system/secrets/taskserver/systemd_tmpfiles.age deleted file mode 100644 index 9ddb060..0000000 --- a/system/secrets/taskserver/systemd_tmpfiles.age +++ /dev/null @@ -1,17 +0,0 @@ ------BEGIN AGE ENCRYPTED FILE----- -YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB2dFg3YmpPeGVxTUoyREF3 -eWhObFJlTVh6K0pyZ3hBUWtNcXgvQ2NIZDJRCkI5K1JCM0VaazJjRmJ3Y0huZHY0 -eGhxOExlM2dqSGxPVlZKak9aQ2NrOG8KLT4gWDI1NTE5IGg5OW1FZ3pKeDBraDcx -MnN0UHBmdldVeDlmK1BoYVc2TUFNNytObmlSanMKUVcxcTRVMHFaenY3UEtkVkRN -VGZ1eVh2UDBqZXJtWkplcnpQQVV5dmFSRQotPiBzc2gtZWQyNTUxOSBPRDhUNGcg -RnRMVXB2aVpkL2VTZ21wb3h4SHFhbFJqV2pKcWVQRGJ3RDJBYWZ2RkkxZwpVcnNr -WVlXQ21Nc2NaUm5QbFhxWmticDE5ZmRmS0VRS0VScm9tUGs1N2lNCi0+IEdwPStW -TVd3LWdyZWFzZSBIciBIdnBbTjI1ClUyeTNTVWhFR2VuSjN5UVpBNDZzclVySUkx -L1NCUTZjMXU3YVlOVk05bFp6YjVFCi0tLSAyeGR3V29DNGszQ0IxU0wyOUxGYmM1 -U0xnTGI5a1pVWFR3THNNRHJOMXNrCh1RrcjPUulX7f1xrZUGoMobWnN6WovrgmeY -FoTo7+JkSedoCKkaDOyP25r4SJe7yUaLrVDUv+gf0KEi2+Bvfh4BIM2N/UsyMmOU -WiFh6UkhQLsePAtfIOd7yl7cDr3adVniulgRSryS1+WDY194BvEtEE/GIbhxAfUz -0Ef90Gp2uOHi3e3dVfy3/0d51Tci3KgWXcMCOe10i+sgnI59OVh6JAT4eykpfESJ -YgBnY45Us80JK1P2lTk8gkHTdvURe2PF4jm/a21XUvXdM7hBN4naSPK7v54at2MQ -xF5C1g== ------END AGE ENCRYPTED FILE----- diff --git a/system/services/default.nix b/system/services/default.nix deleted file mode 100644 index db7ca4f..0000000 --- a/system/services/default.nix +++ /dev/null @@ -1,15 +0,0 @@ -{...}: { - imports = [ - ./invidious - ./invidious-router - ./mail - ./mastodon - ./matrix - ./minecraft - ./miniflux - ./murmur - ./nix - ./restic - ./taskserver - ]; -} diff --git a/system/services/invidious-router/default.nix b/system/services/invidious-router/default.nix deleted file mode 100644 index 8829d3b..0000000 --- a/system/services/invidious-router/default.nix +++ /dev/null @@ -1,51 +0,0 @@ -{pkgsUnstable, ...}: { - services.invidious-router = { - enable = true; - package = pkgsUnstable.invidious-router; - settings = { - app = { - listen = "127.0.0.1:8050"; - enable_youtube_fallback = false; - reload_instance_list_interval = "60s"; - not_available_message = '' - No available invidious instance found! - [link]View this video on YouTube[/link], a proprietary - platform that collects and uses your data without respecting - your privacy. - ''; - }; - api = { - enabled = true; - url = "https://api.invidious.io/instances.json"; - filter_regions = false; - allowed_regions = [ - "AT" - "DE" - "CH" - ]; - }; - healthcheck = { - path = "/watch?v=uSvJaYxRoB4"; - allowed_status_codes = [ - 200 - ]; - timeout = "1s"; - interval = "10s"; - filter_by_response_time = { - enabled = true; - qty_of_top_results = 4; - }; - minimum_ratio = 0.2; - remove_no_ratio = false; - text_not_present = "YouTube is currently trying to block Invidious instances"; - }; - }; - nginx = { - enable = true; - domain = "invidious-router.sils.li"; - extraDomains = [ - "video.fosswelt.org" - ]; - }; - }; -} diff --git a/system/services/invidious/default.nix b/system/services/invidious/default.nix deleted file mode 100644 index 6c587b3..0000000 --- a/system/services/invidious/default.nix +++ /dev/null @@ -1,52 +0,0 @@ -{ - config, - lib, - pkgs, - ... -}: let - cfg = config.services.invidious; -in { - services.invidious = { - enable = true; - database = { - createLocally = true; - }; - domain = "invidious.vhack.eu"; - nginx.enable = true; - extraSettingsFile = "$CREDENTIALS_DIRECTORY/hmac"; - - settings = { - check_tables = true; - db = { - dbname = "invidious"; - user = "invidious"; - }; - }; - }; - systemd.services.invidious.serviceConfig = { - LoadCredential = "hmac:${config.age.secrets.invidiousHmac.path}"; - - ExecStart = let - # taken from the invidious module - settingsFormat = pkgs.formats.json {}; - settingsFile = settingsFormat.generate "invidious-settings" cfg.settings; - - jqFilter = - "." - + lib.optionalString (cfg.database.host != null) "[0].db.password = \"'\"'\"$(cat ${lib.escapeShellArg cfg.database.passwordFile})\"'\"'\"" - + " | .[0]" - + lib.optionalString (cfg.extraSettingsFile != null) " * .[1]"; - - # don't escape extraSettingsFile, to allow variable substitution - jqFiles = - settingsFile - + lib.optionalString (cfg.extraSettingsFile != null) " \"${cfg.extraSettingsFile}\""; - in - lib.mkForce (pkgs.writeScript "start-invidious" '' - #! ${pkgs.dash}/bin/dash - - export INVIDIOUS_CONFIG="$(${pkgs.jq}/bin/jq -s "${jqFilter}" ${jqFiles})" - exec ${cfg.package}/bin/invidious - ''); - }; -} diff --git a/system/services/mail/default.nix b/system/services/mail/default.nix deleted file mode 100644 index c69e6bd..0000000 --- a/system/services/mail/default.nix +++ /dev/null @@ -1,45 +0,0 @@ -{lib, ...}: let - all_admins = [ - "sils@vhack.eu" - "soispha@vhack.eu" - "nightingale@vhack.eu" - ]; - users = import ./users.nix {}; -in { - imports = [ - ./impermanence.nix - ]; - - mailserver = - lib.recursiveUpdate { - enable = true; - fqdn = "server1.vhack.eu"; - - useFsLayout = true; - - extraVirtualAliases = { - "abuse@vhack.eu" = all_admins; - "postmaster@vhack.eu" = all_admins; - "admin@vhack.eu" = all_admins; - }; - - mailDirectory = "/var/lib/mail/vmail"; - dkimKeyDirectory = "/var/lib/mail/dkim"; - sieveDirectory = "/var/lib/mail/sieve"; - backup.snapshotRoot = "/var/lib/mail/backup"; - - enableImap = false; - enableImapSsl = true; - enablePop3 = false; - enablePop3Ssl = true; - # SMTP - enableSubmission = false; - enableSubmissionSsl = true; - openFirewall = true; - - keyFile = "/var/lib/acme/server1.vhack.eu/key.pem"; - certificateScheme = "acme"; - certificateFile = "/var/lib/acme/server1.vhack.eu/fullchain.pem"; - } - users; -} diff --git a/system/services/mail/impermanence.nix b/system/services/mail/impermanence.nix deleted file mode 100644 index 22a5318..0000000 --- a/system/services/mail/impermanence.nix +++ /dev/null @@ -1,46 +0,0 @@ -{...}: { - vhack.persist.directories = [ - { - directory = "/var/lib/mail/backup"; - user = "virtualMail"; - group = "virtualMail"; - mode = "0700"; - } - { - directory = "/var/lib/mail/sieve"; - user = "virtualMail"; - group = "virtualMail"; - mode = "0700"; - } - { - directory = "/var/lib/mail/vmail"; - user = "virtualMail"; - group = "virtualMail"; - mode = "0700"; - } - { - directory = "/var/lib/mail/dkim"; - user = "opendkim"; - group = "opendkim"; - mode = "0700"; - } - { - directory = "/var/lib/postfix/data"; - user = "postfix"; - group = "postfix"; - mode = "0700"; - } - { - directory = "/var/lib/postfix/queue"; - user = "postfix"; - group = "postfix"; - mode = "0700"; - } - { - directory = "/var/lib/rspamd"; - user = "rspamd"; - group = "rspamd"; - mode = "0700"; - } - ]; -} diff --git a/system/services/mail/users.nix b/system/services/mail/users.nix deleted file mode 100644 index 0bae1a7..0000000 --- a/system/services/mail/users.nix +++ /dev/null @@ -1,52 +0,0 @@ -{...}: { - domains = [ - "vhack.eu" - - "s-schoeffel.de" - "b-peetz.de" - - "sils.li" - "nightingale.sils.li" - "sils.sils.li" - ]; - - loginAccounts = { - "sils@vhack.eu" = { - hashedPassword = "$2b$05$RW/Svgk7iGxvP5W7ZwUZ1e.a3fj4fteevb2MtfFYYD0d1DQ17y9Fm"; - }; - "soispha@vhack.eu" = { - hashedPassword = "$2b$05$XX36sJuHNbTFvi8DFldscOeQBHahluSkiUqD9QGzQaET7NJusSuQW"; - }; - - "benedikt.peetz@b-peetz.de" = { - hashedPassword = "$2b$05$MfET8utot2OolPZNASqoDe4VXNoG2chnEWhdfQ2E92mit0TvI2gBy"; - aliases = ["@b-peetz.de"]; - }; - "silas.schoeffel@s-schoeffel.de" = { - hashedPassword = "$2b$05$Qb8rl7ncpCcTbsSdsduJBuOITp8RTD6sfOTjuxJsVtD9vjAYY9n8e"; - aliases = ["@s-schoeffel.de"]; - }; - - "nightingale@vhack.eu" = { - hashedPassword = "$2b$05$nDKVVq1EktKXWqGFhnOLP.plLovXFyvWSuptK9GIkxA5DScKFx6YS"; - aliases = [ - "@nightingale.sils.li" - ]; - }; - "sils@sils.li" = { - hashedPassword = "$2b$05$Ebzh2ZhuWkz1p4tqJ172IejNZg10FtCxPDY4k6umYrpirXg7ezIRq"; - aliases = [ - "@sils.sils.li" - "@sils.li" - ]; - }; - - # Mail-Account used by hosted software - "mastodon@vhack.eu" = { - hashedPassword = "$2b$05$pSby3x2p3cHg0FyAE8IiJ.nYUqtAIR10JA8HNpHwMAiLXqc.ltSK."; - }; - "peertube@vhack.eu" = { - hashedPassword = "$y$j9T$hyWQ8Awd2Xrc6qsK.2hwE1$LxACfaeW.yHGbkQL95dWtID9.zXL/aMwT6lp.yU/0g0"; - }; - }; -} diff --git a/system/services/mastodon/default.nix b/system/services/mastodon/default.nix deleted file mode 100644 index 15b8609..0000000 --- a/system/services/mastodon/default.nix +++ /dev/null @@ -1,79 +0,0 @@ -{ - config, - pkgs, - ... -}: let - emailAddress = "mastodon@vhack.eu"; - applyPatches = pkg: - pkg.overrideAttrs (attrs: { - patches = (attrs.patches or []) ++ [./patches/0001-feat-treewide-Increase-character-limit-to-5000-in-me.patch]; - }); -in { - vhack.persist.directories = [ - { - directory = "/var/lib/mastodon"; - user = "mastodon"; - group = "mastodon"; - mode = "0700"; - } - ]; - - services.mastodon = { - enable = true; - - package = applyPatches pkgs.mastodon; - - # Unstable Mastodon package, used if - # security updates aren't backported. - #package = applyPatches pkgs-unstable.mastodon; - - localDomain = "vhack.eu"; - smtp = { - authenticate = true; - createLocally = false; - fromAddress = emailAddress; - user = emailAddress; - host = "server1.vhack.eu"; - passwordFile = config.age.secrets.mastodonMail.path; - }; - streamingProcesses = 5; # Number of Cores - 1 - extraConfig = { - WEB_DOMAIN = "mastodon.vhack.eu"; - EMAIL_DOMAIN_ALLOWLIST = "vhack.eu|sils.li"; - }; - }; - - services.nginx = { - enable = true; - recommendedProxySettings = true; # required for redirections to work - virtualHosts = { - ${config.services.mastodon.extraConfig.WEB_DOMAIN} = { - root = "${config.services.mastodon.package}/public/"; - # mastodon only supports https, but you can override this if you offload tls elsewhere. - forceSSL = true; - enableACME = true; - - locations = { - "/system/".alias = "/var/lib/mastodon/public-system/"; - "/".tryFiles = "$uri @proxy"; - "@proxy" = { - proxyPass = "http://unix:/run/mastodon-web/web.socket"; - proxyWebsockets = true; - }; - "/api/v1/streaming/" = { - proxyPass = "http://unix:/run/mastodon-streaming/streaming.socket"; - proxyWebsockets = true; - }; - }; - }; - - "vhack.eu" = { - locations."/.well-known/webfinger".return = "301 https://${config.services.mastodon.extraConfig.WEB_DOMAIN}$request_uri"; - }; - }; - }; - - users.groups.${config.services.mastodon.group}.members = [ - config.services.nginx.user - ]; -} diff --git a/system/services/matrix/default.nix b/system/services/matrix/default.nix deleted file mode 100644 index 043d9c0..0000000 --- a/system/services/matrix/default.nix +++ /dev/null @@ -1,133 +0,0 @@ -{ - config, - pkgs, - ... -}: let - fqdn = "matrix.vhack.eu"; - clientConfig."m.homeserver".base_url = "https://${fqdn}"; - serverConfig."m.server" = "${fqdn}:443"; - mkWellKnown = data: '' - add_header Content-Type application/json; - add_header Access-Control-Allow-Origin *; - return 200 '${builtins.toJSON data}'; - ''; -in { - networking.firewall.allowedTCPPorts = [80 443]; - - vhack.persist.directories = [ - { - directory = "/var/lib/matrix"; - user = "matrix-synapse"; - group = "matrix-synapse"; - mode = "0700"; - } - { - directory = "/var/lib/mautrix-whatsapp"; - user = "mautrix-whatsapp"; - group = "matrix-synapse"; - mode = "0750"; - } - ]; - systemd.tmpfiles.rules = [ - "d /etc/matrix 0755 matrix-synapse matrix-synapse" - ]; - - services = { - postgresql = { - enable = true; - initialScript = pkgs.writeText "synapse-init.sql" '' - --Matrix: - CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse'; - CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse" - TEMPLATE template0 - LC_COLLATE = "C" - LC_CTYPE = "C"; - - --Whatsapp-bridge: - CREATE ROLE "mautrix-whatsapp" WITH LOGIN PASSWORD 'whatsapp'; - CREATE DATABASE "mautrix-whatsapp" WITH OWNER "mautrix-whatsapp" - TEMPLATE template0 - LC_COLLATE = "C" - LC_CTYPE = "C"; - ''; - }; - - nginx = { - enable = true; - recommendedTlsSettings = true; - recommendedOptimisation = true; - recommendedGzipSettings = true; - recommendedProxySettings = true; - virtualHosts = { - "vhack.eu" = { - enableACME = true; - forceSSL = true; - locations = { - "/.well-known/matrix/server".extraConfig = mkWellKnown serverConfig; - "/.well-known/matrix/client".extraConfig = mkWellKnown clientConfig; - }; - }; - "matrix.vhack.eu" = { - enableACME = true; - forceSSL = true; - locations = { - "/".return = "404"; - "/_matrix".proxyPass = "http://[::1]:8008"; - "/_synapse/client".proxyPass = "http://[::1]:8008"; - }; - }; - }; - }; - - mautrix-whatsapp = { - # FIXME(@bpeetz): This was disabled because `mautrix-whatsapp` dependends on libolm. - # Re-enable it, when this has changed. <2024-09-06> - enable = false; - settings = { - appservice = { - database = { - type = "postgres"; - uri = "postgres:///mautrix-whatsapp?host=/run/postgresql"; - }; - whatsapp = { - # TODO: See https://github.com/tulir/whatsmeow/blob/efc632c008604016ddde63bfcfca8de4e5304da9/binary/proto/def.proto#L43-L64 for a list. - # This also determines the WhatsApp icon - browser_name = "unknown"; - }; - }; - homeserver.address = "https://matrix.vhack.eu"; - bridge.permissions = { - "@soispha:vhack.eu" = "admin"; - "@sils:vhack.eu" = "admin"; - "@nightingale:vhack.eu" = "admin"; - }; - }; - }; - - matrix-synapse = { - enable = true; - dataDir = "/var/lib/matrix"; - configFile = "/etc/matrix/matrix.conf"; - settings = { - media_store_path = "/var/lib/matrix/media_store"; - registration_shared_secret_path = "${config.age.secrets.matrix-synapse_registration_shared_secret.path}"; - server_name = "vhack.eu"; - listeners = [ - { - port = 8008; - bind_addresses = ["::1"]; - type = "http"; - tls = false; - x_forwarded = true; - resources = [ - { - names = ["client" "federation"]; - compress = true; - } - ]; - } - ]; - }; - }; - }; -} diff --git a/system/services/minecraft/default.nix b/system/services/minecraft/default.nix deleted file mode 100644 index 9bc98b9..0000000 --- a/system/services/minecraft/default.nix +++ /dev/null @@ -1,37 +0,0 @@ -{lib, ...}: { - vhack.persist.directories = [ - { - directory = "/var/lib/minecraft"; - user = "minecraft"; - group = "minecraft"; - mode = "0700"; - } - ]; - - nixpkgs.config.allowUnfreePredicate = pkg: - builtins.elem (lib.getName pkg) [ - "minecraft-server" - ]; - services.minecraft-server = { - enable = true; - declarative = true; - eula = true; - dataDir = "/var/lib/minecraft"; - openFirewall = true; - jvmOpts = "-Xmx8192M -Xms8192M"; - whitelist = { - ShadyCraft = "7995eea5-b648-41c4-9b0f-7fc082565952"; - Nightingale768 = "1125d077-6709-44b2-9be0-587aec772e7a"; - Sirius_Black123 = "f5f66fc5-f287-434e-a03f-2480f998e76f"; - DerDaHalt = "09c72cb7-cab9-444f-af55-616fcf93822c"; - }; - serverProperties = { - player-idle-timeout = 30; - white-list = true; - difficulty = 3; - gamemode = "survival"; - enforce-whitelist = true; - simulation-distance = 10; - }; - }; -} diff --git a/system/services/miniflux/default.nix b/system/services/miniflux/default.nix deleted file mode 100644 index 9a0f2bc..0000000 --- a/system/services/miniflux/default.nix +++ /dev/null @@ -1,22 +0,0 @@ -{config, ...}: { - services.miniflux = { - enable = true; - config = { - LISTEN_ADDR = "127.0.0.1:5892"; - }; - adminCredentialsFile = config.age.secrets.minifluxAdmin.path; - }; - - services.nginx = { - enable = true; - virtualHosts."miniflux.vhack.eu" = { - locations."/".proxyPass = "http://${config.services.miniflux.config.LISTEN_ADDR}"; - - enableACME = true; - forceSSL = true; - serverAliases = [ - "rss.vhack.eu" - ]; - }; - }; -} diff --git a/system/services/murmur/default.nix b/system/services/murmur/default.nix deleted file mode 100644 index dec79ba..0000000 --- a/system/services/murmur/default.nix +++ /dev/null @@ -1,50 +0,0 @@ -{...}: let - murmurStore = "/var/lib/murmur"; -in { - vhack.persist.directories = [ - { - directory = "/var/lib/murmur"; - user = "murmur"; - group = "murmur"; - mode = "0700"; - } - ]; - - services.murmur = { - enable = true; - openFirewall = true; - welcometext = '' - <b>You never get a second chance to make a first impression</b><br> - - The entire team of [name of the company] is thrilled to welcome you on board. We hope you’ll do some amazing work here! - ''; - sslKey = "${murmurStore}/key.pem"; - sslCert = "${murmurStore}/fullchain.pem"; - - registerUrl = "vhack.eu"; - registerName = "vhack"; - registerHostname = "mumble.vhack.eu"; - hostName = "mumble.vhack.eu"; - clientCertRequired = true; - bandwidth = 7200000; - }; - - security.acme.certs.murmur = { - domain = "mumble.vhack.eu"; - postRun = - /* - bash - */ - '' - set -x - rm "${murmurStore}/key.pem" - rm "${murmurStore}/fullchain.pem" - - cp key.pem "${murmurStore}"; - cp fullchain.pem "${murmurStore}"; - - chown murmur:murmur "${murmurStore}/key.pem" - chown murmur:murmur "${murmurStore}/fullchain.pem" - ''; - }; -} diff --git a/system/services/nix/default.nix b/system/services/nix/default.nix deleted file mode 100644 index 13be0f0..0000000 --- a/system/services/nix/default.nix +++ /dev/null @@ -1,23 +0,0 @@ -{ - config, - lib, - pkgs, - ... -}: { - nix = { - # gc = { - # automatic = true; - # dates = "daily"; - # options = "--delete-older-than 3"; - # }; - settings = { - auto-optimise-store = true; - experimental-features = ["nix-command" "flakes"]; - trusted-users = [ - "root" - "nixremote" - "@wheel" - ]; - }; - }; -} diff --git a/system/services/restic/default.nix b/system/services/restic/default.nix deleted file mode 100644 index cfeaca3..0000000 --- a/system/services/restic/default.nix +++ /dev/null @@ -1,50 +0,0 @@ -{ - config, - pkgs, - ... -}: { - services.restic.backups = let - snapshots = "/srv/snapshots"; - boxUser = "u384702-sub2"; - postgresUser = "postgres"; - in { - storagebox = { - initialize = true; - backupPrepareCommand = '' - ${pkgs.sudo}/bin/sudo -u ${postgresUser} ${pkgs.postgresql}/bin/pg_dumpall --clean --if-exists --quote-all-identifiers > /srv/db_backup.sql - - [ -d /srv/snapshots ] || ${pkgs.btrfs-progs}/bin/btrfs subvolume create /srv/snapshots; - [ -d /srv/snapshots/srv ] && ${pkgs.btrfs-progs}/bin/btrfs subvolume delete /srv/snapshots/srv; - ${pkgs.btrfs-progs}/bin/btrfs subvolume snapshot -r /srv /srv/snapshots/srv; - - # dump() { - # # compression: - # # pg_dump -F t -v "$1" | xz -z -9 -e -T0 > "db_$1.tar.xz" - # pg_dump -v "$1" > "db_$1.tar.xz" - # } - # # List all databases, and dump each of them in its own file - # # psql --list --csv | while read -r line; do echo "$line" | grep ','; done | while IFS=, read -r name _; do echo "$name"; done | sed '1d' | while read -r db_name; do dump "$db_name"; done - ''; - paths = [ - snapshots - ]; - exclude = [ - ".snapshots" - "/var/lib/postgresql" # included in the db dump - ]; - extraBackupArgs = [ - "--verbose" # spam log - ]; - passwordFile = config.age.secrets.resticpass.path; - extraOptions = [ - "rclone.program='ssh -p 23 ${boxUser}@${boxUser}.your-storagebox.de -i ${config.age.secrets.resticssh.path}'" - ]; - repository = "rclone: "; # There is only one repository served - timerConfig = { - Requires = "network-online.target"; - OnCalendar = "daily"; - Persistent = true; - }; - }; - }; -} diff --git a/system/services/taskserver/certs/README.md b/system/services/taskserver/certs/README.md deleted file mode 100644 index 8ff0e44..0000000 --- a/system/services/taskserver/certs/README.md +++ /dev/null @@ -1,42 +0,0 @@ -> This is taken from: https://github.com/GothenburgBitFactory/taskserver/blob/9794cff61e56bdfb193c6aa4cebb57970ac68aef/pki/README - -PKI is a complex subject. These scripts and this description are not intended -to be a complete and accurate example of PKI. - -Ideally you would purchase a server cert signed by a known CA, such as one of -the following: - -- Symantec -- Comodo -- GoDaddy -- GlobalSign -- (Let's Encrypt) - -That cert would need the 'encryption_key' and 'signing_key' attributes. -Using that server cert, you would then issue a server CRL and client keys. - -If you are developing, testing, or running your own private server, you may -choose instead to generate the above yourself. In this case you would generate -a CA key and cert, then use that to generate a server key, cert, and CRL. Then -you would use the server key and cert to create a client key and cert. But as -there is no trusted CA in this example, just yourself, the resultant client key -and cert will not be trusted by anyone, for good reasons. - -Note, you can inspect any cert with the command: - -``` -$ gnutls-certtool -i --infile $CERT -``` - -There is a 'generate' script here that will perform the above steps. Take a -look at it to see the individual steps it takes to generate the proper set of -keys and certs. - -Note that you need to modify the 'vars' file to provide your own identity and -chosen parameters. - -Validate a certificate with: - -``` -$ gnutls-certtool --verify --infile client.cert.pem --load-ca-certificate ca.cert.pem -``` diff --git a/system/services/taskserver/certs/ca.cert.pem b/system/services/taskserver/certs/ca.cert.pem deleted file mode 100644 index 76c0435..0000000 --- a/system/services/taskserver/certs/ca.cert.pem +++ /dev/null @@ -1,83 +0,0 @@ ------BEGIN CERTIFICATE----- -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 -53Nn1zkqWwd9Ws9D7zQwog== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 -WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu -ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc -h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ -0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U -A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW -T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH -B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC -B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv -KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn -OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn -jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw -qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI -rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq -hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL -ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ -3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK -NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 -ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur -TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC -jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc -oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq -4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA -mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d -emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= ------END CERTIFICATE----- diff --git a/system/services/taskserver/certs/ca.key.pem.gpg b/system/services/taskserver/certs/ca.key.pem.gpg deleted file mode 100644 index 8abd59a..0000000 --- a/system/services/taskserver/certs/ca.key.pem.gpg +++ /dev/null Binary files differdiff --git a/system/services/taskserver/certs/check_expire b/system/services/taskserver/certs/check_expire deleted file mode 100755 index 39f3291..0000000 --- a/system/services/taskserver/certs/check_expire +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env nix -#! nix shell nixpkgs#openssl nixpkgs#dash --command dash - -cd "$(dirname "$0")" || { - echo "No dir name?!" - exit 1 -} - -for cert in *.cert.pem; do - echo "$cert" - openssl x509 -noout -in "$cert" -dates - echo -done diff --git a/system/services/taskserver/certs/generate b/system/services/taskserver/certs/generate deleted file mode 100755 index c3b58ae..0000000 --- a/system/services/taskserver/certs/generate +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env nix-shell -#! nix-shell -i dash --packages openssl gnutls dash -#! nix-shell --impure - -# For a public or production server, purchase a cert from a known CA, and skip -# the next step. - -# For development, testing and personal server management, create a CA key and -# cert, and use that to generate a server key and cert. Creates: -# ca.key.pem -# ca.cert.pem -# server.key.pem -# server.cert.pem - -GENERATION_LOCATION="/run/user/$(id -u)/taskserver/certs" -BASEDIR="$(dirname "$0")" -cd "$BASEDIR" || { - echo "(BUG?) No basedir ('$BASEDIR')" 1>&2 - exit 1 -} - -ca=false -crl=false -clients=false - -for arg in "$@"; do - case "$arg" in - "--ca") - ca=true - ;; - "--crl") - crl=true - ;; - "--clients") - clients=true - ;; - esac -done - -# `ca.cert.pem` is not on this list, as it would otherwise get deleted in the `rm` on the -# second-to last line -set -- ./vars ./generate.ca ./generate.crl ./generate.client ./ca.key.pem.gpg ./isrgrootx1.pem - -mkdir --parents "$GENERATION_LOCATION" -cp "$@" ./ca.cert.pem "$GENERATION_LOCATION" -cd "$GENERATION_LOCATION" || echo "(BUG?) No possible location fould!" 1>&2 - -gpg --decrypt ca.key.pem.gpg >ca.key.pem - -[ "$ca" = true ] && ./generate.ca -cat ./isrgrootx1.pem >>./ca.cert.pem - -# Generate a certificate revocation list (CRL). The initial CRL is empty, but -# can grow over time. Creates: -# server.crl.pem - -[ "$crl" = true ] && ./generate.crl - -# The above is sufficient to operate a server. You now need to run a client cert creation -# process per client; Add the required client names and uncomment -# ./generate.client <client_name> -# -# -# Creates: -# <client_name>.key.pem -# <client_name>.cert.pem -# -[ "$clients" = true ] && ./generate.client soispha -[ "$clients" = true ] && ./generate.client android-mobile -[ "$clients" = true ] && ./generate.client android-tab - -rm "$@" "./ca.key.pem" -echo "(INFO) Look for the keys at: $GENERATION_LOCATION" - -# vim: ft=sh diff --git a/system/services/taskserver/certs/generate.ca b/system/services/taskserver/certs/generate.ca deleted file mode 100755 index eb0dd5c..0000000 --- a/system/services/taskserver/certs/generate.ca +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/sh - -# Take the correct binary to create the certificates -CERTTOOL=$(command -v gnutls-certtool 2>/dev/null || command -v certtool 2>/dev/null) -if [ -z "$CERTTOOL" ]; then - echo "ERROR: No certtool found" >&2 - exit 1 -fi - -. ./vars - -if ! [ -f ca.key.pem ]; then - # Create a CA key. - $CERTTOOL \ - --generate-privkey \ - --sec-param $SEC_PARAM \ - --outfile ca.key.pem -fi - -chmod 600 ca.key.pem - -if ! [ -f ca.template ]; then - # Sign a CA cert. - cat <<EOF >ca.template -organization = $ORGANIZATION -cn = $CN CA -country = $COUNTRY -expiration_days = $EXPIRATION_DAYS -ca -EOF -#state = $STATE -#locality = $LOCALITY -fi - -if ! [ -f ca.cert.pem ]; then - $CERTTOOL \ - --generate-self-signed \ - --load-privkey ca.key.pem \ - --template ca.template \ - --outfile ca.cert.pem -fi - -chmod 600 ca.cert.pem diff --git a/system/services/taskserver/certs/generate.crl b/system/services/taskserver/certs/generate.crl deleted file mode 100755 index e9f6715..0000000 --- a/system/services/taskserver/certs/generate.crl +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/sh - -# Take the correct binary to create the certificates -CERTTOOL=$(command -v gnutls-certtool 2>/dev/null || command -v certtool 2>/dev/null) -if [ -z "$CERTTOOL" ] -then - echo "ERROR: No certtool found" >&2 - exit 1 -fi - -. ./vars - -if ! [ -f crl.template ] -then - # CRL - Certificate Revocation List - cat <<EOF >crl.template -expiration_days = $EXPIRATION_DAYS -EOF -fi - -if ! [ -f server.crl.pem ] -then - $CERTTOOL \ - --generate-crl \ - --load-ca-privkey ca.key.pem \ - --load-ca-certificate ca.cert.pem \ - --template crl.template \ - --outfile server.crl.pem -fi - -chmod 600 server.crl.pem - -# To create a CRL that contains some revoked certificates, place the -# certificates in a file and use --load-certificate as follows: -# $CERTTOOL \ -# --generate-crl \ -# --load-ca-privkey ca.key.pem \ -# --load-ca-certificate ca.cert.pem \ -# --load-certificate revoked-certs.pem - -# To verify a CRL: -# $CERTTOOL --verify-crl --load-ca-certificate ca.cert.pem --infile server.crl.pem diff --git a/system/services/taskserver/certs/isrgrootx1.pem b/system/services/taskserver/certs/isrgrootx1.pem deleted file mode 100644 index b85c803..0000000 --- a/system/services/taskserver/certs/isrgrootx1.pem +++ /dev/null @@ -1,31 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 -WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu -ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc -h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ -0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U -A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW -T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH -B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC -B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv -KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn -OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn -jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw -qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI -rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq -hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL -ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ -3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK -NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 -ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur -TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC -jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc -oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq -4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA -mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d -emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= ------END CERTIFICATE----- diff --git a/system/services/taskserver/certs/vars b/system/services/taskserver/certs/vars deleted file mode 100644 index 50d753a..0000000 --- a/system/services/taskserver/certs/vars +++ /dev/null @@ -1,7 +0,0 @@ -SEC_PARAM=ultra -EXPIRATION_DAYS=365 -ORGANIZATION="Vhack" -CN=taskserver.vhack.eu -COUNTRY=EU -#STATE="Germany" -#LOCALITY="Göteborg" diff --git a/system/services/taskserver/default.nix b/system/services/taskserver/default.nix deleted file mode 100644 index 04b6a8b..0000000 --- a/system/services/taskserver/default.nix +++ /dev/null @@ -1,56 +0,0 @@ -{config, ...}: let - taskStore = "/var/lib/taskserver"; -in { - environment.etc = { - "tmpfiles.d/taskserver.conf".source = config.age.secrets.taskserverSystemdTmpfiles.path; - }; - - vhack.persist.directories = [ - "/var/lib/taskserver" - ]; - - services.taskserver = { - enable = true; - pki.manual = { - ca.cert = ./certs/ca.cert.pem; - server = { - cert = "${taskStore}/fullchain.pem"; - key = "${taskStore}/key.pem"; - }; - }; - - debug = false; - ipLog = false; - trust = "strict"; - - organisations = import ./organisations.nix; - openFirewall = true; - fqdn = "taskserver.vhack.eu"; - - # This should tell taskd to bind to both ipv6 and ipv4 domains: - # This will ONLY work when the kernel option `sys.net.ipv6.bindv6only` is false - listenHost = "::"; - }; - boot.kernelParams = [ - "sys.net.ipv6.bindv6only=0" - ]; - - security.acme.certs.taskserver = { - domain = "taskserver.vhack.eu"; - postRun = - /* - bash - */ - '' - set -x - rm "${taskStore}/key.pem" - rm "${taskStore}/fullchain.pem" - - cp key.pem "${taskStore}"; - cp fullchain.pem "${taskStore}"; - - chown taskd:taskd "${taskStore}/key.pem" - chown taskd:taskd "${taskStore}/fullchain.pem" - ''; - }; -} diff --git a/system/services/taskserver/organisations.nix b/system/services/taskserver/organisations.nix deleted file mode 100644 index c3ad966..0000000 --- a/system/services/taskserver/organisations.nix +++ /dev/null @@ -1,14 +0,0 @@ -{ - vhack = { - users = [ - "soispha" - ]; - }; - soispha = { - users = [ - "soispha" - "android-mobile" - "android-tab" - ]; - }; -} diff --git a/tests/by-name/ba/back/test.nix b/tests/by-name/ba/back/test.nix index 63f2837..85cb611 100644 --- a/tests/by-name/ba/back/test.nix +++ b/tests/by-name/ba/back/test.nix @@ -8,9 +8,19 @@ nixLib, ... }: let - gitRepoPath = "/srv/test/repo"; - domain = "server"; + + sshKeys = + import ../../gi/git-server/ssh_keys.nix {inherit pkgs;}; + + gitoliteAdminConfSnippet = pkgs.writeText "gitolite-admin-conf-snippet" '' + repo CREATOR/[a-zA-Z0-9].* + C = @all + RW+ = CREATOR + RW = WRITERS + R = READERS + option user-configs = cgit\.owner cgit\.desc cgit\.section cgit\.homepage + ''; in nixos-lib.runTest { hostPkgs = pkgs; # the Nixpkgs package set used outside the VMs @@ -26,7 +36,7 @@ in nodes = { server = {config, ...}: { - environment.systemPackages = [pkgs.git pkgs.git-bug pkgs.gawk]; + environment.systemPackages = [pkgs.git]; imports = extraModules @@ -35,28 +45,46 @@ in ]; vhack = { + persist.enable = true; + openssh.enable = true; nginx = { enable = true; selfsign = true; }; git-server = { enable = true; + domain = "git.${domain}"; + gitolite.adminPubkey = sshKeys.admin.pub; }; back = { enable = true; - repositories = { - "${gitRepoPath}" = { - enable = true; - domain = "${domain}"; - port = 9220; - }; + domain = "issues.${domain}"; + + settings = { + scan_path = "${config.services.gitolite.dataDir}/repositories"; + project_list = "${config.services.gitolite.dataDir}/projects.list"; }; }; }; }; - client = {...}: { - environment.systemPackages = [pkgs.curl]; + client = {nodes, ...}: { + environment.systemPackages = [pkgs.git pkgs.curl pkgs.git-bug pkgs.gawk]; + programs.ssh.extraConfig = '' + Host * + UserKnownHostsFile /dev/null + StrictHostKeyChecking no + # there's nobody around that can input password + PreferredAuthentications publickey + ''; + users.users.alice = {isNormalUser = true;}; + networking.hosts = { + "${nodes.server.networking.primaryIPAddress}" = [ + "git.${domain}" + "issues.${domain}" + "${domain}" + ]; + }; }; }; @@ -67,16 +95,64 @@ in '' start_all() - with subtest("can setup git-bug issues on server"): - server.succeed("${pkgs.writeShellScript "setup-git-repo" '' - set -ex + with subtest("can setup ssh keys on client"): + client.succeed( + "mkdir -p ~root/.ssh", + "cp ${sshKeys.admin.priv} ~root/.ssh/id_ed25519", + "chmod 600 ~root/.ssh/id_ed25519", + ) + client.succeed( + "sudo -u alice mkdir -p ~alice/.ssh", + "sudo -u alice cp ${sshKeys.alice.priv} ~alice/.ssh/id_ed25519", + "sudo -u alice chmod 600 ~alice/.ssh/id_ed25519", + ) + + with subtest("gitolite server starts"): + server.wait_for_unit("gitolite-init.service") + server.wait_for_unit("sshd.service") + client.succeed("ssh -n git@git.${domain} info") + + + with subtest("admin can clone and configure gitolite-admin.git"): + client.succeed("${pkgs.writeShellScript "setup-gitolite-admin.git" '' + set -xe + + git clone git@git.${domain}:gitolite-admin.git + git config --global user.name 'System Administrator' + git config --global user.email root\@domain.example + + cp ${sshKeys.alice.pub} gitolite-admin/keydir/alice.pub - mkdir --parents "${gitRepoPath}" - cd "${gitRepoPath}" + (cd gitolite-admin && git switch -c master && git branch -D main) - git init + (cd gitolite-admin && git add . && git commit -m 'Add keys for alice' && git push -u origin master) + cat ${gitoliteAdminConfSnippet} >> gitolite-admin/conf/gitolite.conf + (cd gitolite-admin && git add . && git commit -m 'Add support for wild repos' && git push) + (cd gitolite-admin && git push -d origin main) + ''}") + + with subtest("alice can create a repo"): + client.succeed("sudo -u alice ${pkgs.writeShellScript "alice-create-repo" '' + set -xe - git bug user create --avatar "" --email "test@email.org" --name "test user" --non-interactive + mkdir --parents ./alice/repo1 && cd alice/repo1; + + git init --initial-branch main + echo "# Alice's Repo" > README.md + git add README.md + git -c user.name=Alice -c user.email=alice@domain.example commit -m 'Add readme' + + git remote add origin git@git.${domain}:alice/repo1.git + git push --set-upstream origin main + ''}") + + with subtest("can setup git-bug issues in alice/repo1"): + client.succeed("sudo -u alice ${pkgs.writeShellScript "setup-git-repo" '' + set -ex + + cd alice/repo1 + + git bug user create --avatar "" --email "alice@server.org" --name "alice" --non-interactive git bug add \ --title "Some bug title" \ @@ -98,24 +174,32 @@ in git bug comment add --message "Some comment message" --non-interactive git bug comment add --message "Second comment message" --non-interactive - # NOTE(@bpeetz): Currently, the `back` module assumes that the git user can write - # to the repository, as such we need to provide write access here <2024-12-24> - chown --recursive git:git "${gitRepoPath}" + # TODO: This should use `git bug push`, but their ssh implementation is just + # too special to work in a VM test <2025-03-08> + git push origin +refs/bugs/* + git push origin +refs/identities/* + + ssh git@${domain} -- config alice/repo1 --add cgit.owner Alice + ssh git@${domain} -- perms alice/repo1 + READERS @all ''}") with subtest("back server starts"): - server.wait_for_unit("${builtins.replaceStrings ["/"] ["_"] "back-${domain}.service"}") + server.wait_for_unit("back.service") with subtest("client can access the server"): client.succeed("${pkgs.writeShellScript "curl-back" '' set -xe - curl --insecure --silent --fail --show-error "https://${domain}/issues/open" --output /root/issues.html - - grep -- '- 2 comments' /root/issues.html + curl --insecure --fail --show-error "https://issues.${domain}/alice/repo1.git/issues/open" --output /root/issues.html grep -- 'Second bug title' /root/issues.html - ''}") + + curl --insecure --fail --show-error "https://issues.${domain}/" --output /root/repos.html + grep -- 'repo' /root/repos.html + grep -- "<No description>" /root/repos.html + grep -- '<span class="user-name">Alice</span>' /root/repos.html + ''} >&2") client.copy_from_vm("/root/issues.html", ""); + client.copy_from_vm("/root/repos.html", ""); ''; } diff --git a/tests/by-name/dn/dns/test.nix b/tests/by-name/dn/dns/test.nix new file mode 100644 index 0000000..01d8833 --- /dev/null +++ b/tests/by-name/dn/dns/test.nix @@ -0,0 +1,129 @@ +# Inspired by this file: /nixpkgs/nixos/tests/nsd.nix +{ + nixos-lib, + pkgsUnstable, + nixpkgs-unstable, + vhackPackages, + pkgs, + extraModules, + nixLib, + ... +}: let + common = {...}: { + networking.firewall.enable = false; + networking.dhcpcd.enable = false; + }; + + mkClient = version: { + lib, + nodes, + ... + }: { + environment.systemPackages = [pkgs.dig pkgs.dig.dnsutils]; + + imports = [common]; + networking.nameservers = lib.mkForce [ + (lib.head nodes.server.networking.interfaces.eth1."${version}".addresses).address + ]; + }; +in + nixos-lib.runTest { + hostPkgs = pkgs; # the Nixpkgs package set used outside the VMs + + name = "dns"; + + node = { + specialArgs = {inherit pkgsUnstable vhackPackages nixpkgs-unstable nixLib;}; + + # Use the nixpkgs as constructed by the `nixpkgs.*` options + pkgs = null; + }; + + nodes = { + server = { + config, + lib, + ... + }: { + imports = + extraModules + ++ [ + ../../../../modules + common + ]; + + vhack = { + dns = { + enable = true; + interfaces = [ + (lib.head config.networking.interfaces.eth1.ipv4.addresses).address + (lib.head config.networking.interfaces.eth1.ipv6.addresses).address + ]; + zones = { + "example.com" = { + SOA = { + nameServer = "ns"; + adminEmail = "admin@example.com"; + serial = 2024012301; + }; + + useOrigin = false; + NS = [ + "ns.example.com." + ]; + + subdomains = { + ns = { + A = ["192.168.1.3"]; + }; + ipv4 = { + A = ["1.2.3.4"]; + }; + ipv6 = { + AAAA = ["dead:beef::1"]; + }; + openpgpkey = { + TXT = ["Hi!"]; + }; + }; + }; + }; + }; + }; + }; + + clientV4 = mkClient "ipv4"; + clientV6 = mkClient "ipv6"; + }; + + testScript = {nodes, ...}: + /* + python + */ + '' + start_all() + + clientV4.wait_for_unit("network.target") + clientV6.wait_for_unit("network.target") + server.wait_for_unit("nsd.service") + + def assert_host(ipVersion, dnsRecordType, dnsQuery, expected): + self = clientV4 if ipVersion == 4 else clientV6 + out = self.succeed(f"host -{ipVersion} -t {dnsRecordType} {dnsQuery}").rstrip() + self.log(f"output: {out}") + import re + assert re.search(expected, out), f"DNS IPv{ipVersion} dnsQuery on {dnsQuery} gave '{out}' instead of '{expected}'" + + + for ipv in 4, 6: + with subtest(f"IPv{ipv}"): + assert_host(ipv, "a", "example.com", "has no [^ ]+ record") + assert_host(ipv, "aaaa", "example.com", "has no [^ ]+ record") + + assert_host(ipv, "soa", "example.com", "SOA.*?admin\\.example\\.com") + assert_host(ipv, "a", "ipv4.example.com", "address 1.2.3.4$") + assert_host(ipv, "aaaa", "ipv6.example.com", "address dead:beef::1$") + + assert_host(ipv, "txt", "openpgpkey.example.com", "descriptive text \"Hi!\"$") + ''; + } diff --git a/tests/by-name/em/email-dns/nodes/acme/certs/generate b/tests/by-name/em/email-dns/nodes/acme/certs/generate new file mode 100755 index 0000000..0d6258e --- /dev/null +++ b/tests/by-name/em/email-dns/nodes/acme/certs/generate @@ -0,0 +1,66 @@ +#! /usr/bin/env nix-shell +#! nix-shell -p gnutls -p dash -i dash --impure +# shellcheck shell=dash + +# For development and testing. +# Create a CA key and cert, and use that to generate a server key and cert. +# Creates: +# ca.key.pem +# ca.cert.pem +# server.key.pem +# server.cert.pem + +export SEC_PARAM=ultra +export EXPIRATION_DAYS=123456 +export ORGANIZATION="Vhack.eu Test Keys" +export COUNTRY=EU +export SAN="acme.test" +export KEY_TYPE="ed25519" + +BASEDIR="$(dirname "$0")" +GENERATION_LOCATION="$BASEDIR/output" +cd "$BASEDIR" || { + echo "(BUG?) No basedir ('$BASEDIR')" 1>&2 + exit 1 +} + +ca=false +clients=false + +usage() { + echo "Usage: $0 --ca|--clients" + exit 2 +} + +if [ "$#" -eq 0 ]; then + usage +fi + +for arg in "$@"; do + case "$arg" in + "--ca") + ca=true + ;; + "--clients") + clients=true + ;; + *) + usage + ;; + esac +done + +[ -d "$GENERATION_LOCATION" ] || mkdir --parents "$GENERATION_LOCATION" +cd "$GENERATION_LOCATION" || echo "(BUG?) No generation location fould!" 1>&2 + +[ "$ca" = true ] && ../generate.ca + +# Creates: +# <client_name>.key.pem +# <client_name>.cert.pem +# +[ "$clients" = true ] && ../generate.client "acme.test" + +echo "(INFO) Look for the keys at: $GENERATION_LOCATION" + +# vim: ft=sh diff --git a/tests/by-name/em/email-dns/nodes/acme/certs/generate.ca b/tests/by-name/em/email-dns/nodes/acme/certs/generate.ca new file mode 100755 index 0000000..92832c5 --- /dev/null +++ b/tests/by-name/em/email-dns/nodes/acme/certs/generate.ca @@ -0,0 +1,38 @@ +#! /usr/bin/env sh + +# Take the correct binary to create the certificates +CERTTOOL=$(command -v gnutls-certtool 2>/dev/null || command -v certtool 2>/dev/null) +if [ -z "$CERTTOOL" ]; then + echo "ERROR: No certtool found" >&2 + exit 1 +fi + +# Create a CA key. +$CERTTOOL \ + --generate-privkey \ + --sec-param "$SEC_PARAM" \ + --key-type "$KEY_TYPE" \ + --outfile ca.key.pem + +chmod 600 ca.key.pem + +# Sign a CA cert. +cat <<EOF >ca.template +country = $COUNTRY +dns_name = "$SAN" +expiration_days = $EXPIRATION_DAYS +organization = $ORGANIZATION +ca +EOF +#state = $STATE +#locality = $LOCALITY + +$CERTTOOL \ + --generate-self-signed \ + --load-privkey ca.key.pem \ + --template ca.template \ + --outfile ca.cert.pem + +chmod 600 ca.cert.pem + +# vim: ft=sh diff --git a/system/services/taskserver/certs/generate.client b/tests/by-name/em/email-dns/nodes/acme/certs/generate.client index 4f0e503..5930298 100755 --- a/system/services/taskserver/certs/generate.client +++ b/tests/by-name/em/email-dns/nodes/acme/certs/generate.client @@ -1,54 +1,44 @@ -#!/bin/sh +#! /usr/bin/env sh # Take the correct binary to create the certificates CERTTOOL=$(command -v gnutls-certtool 2>/dev/null || command -v certtool 2>/dev/null) -if [ -z "$CERTTOOL" ] -then - echo "ERROR: No certtool found" >&2 - exit 1 +if [ -z "$CERTTOOL" ]; then + echo "ERROR: No certtool found" >&2 + exit 1 fi -. ./vars - NAME=client -if [ $# -gt 0 ] -then - NAME=$1 +if [ $# -gt 0 ]; then + NAME="$1" fi -if ! [ -f "$NAME".key.pem ] -then - # Create a client key. - $CERTTOOL \ +# Create a client key. +$CERTTOOL \ --generate-privkey \ - --sec-param $SEC_PARAM \ + --sec-param "$SEC_PARAM" \ + --key-type "$KEY_TYPE" \ --outfile "$NAME".key.pem -fi chmod 600 "$NAME".key.pem -if ! [ -f "$NAME".template ] -then - # Sign a client cert with the key. - cat <<EOF >"$NAME".template -organization = $ORGANIZATION -cn = $CN +# Sign a client cert with the key. +cat <<EOF >"$NAME".template +dns_name = "$NAME" +dns_name = "$SAN" expiration_days = $EXPIRATION_DAYS -tls_www_client +organization = $ORGANIZATION encryption_key signing_key EOF -fi -if ! [ -f "$NAME".cert.pem ] -then - $CERTTOOL \ +$CERTTOOL \ --generate-certificate \ --load-privkey "$NAME".key.pem \ --load-ca-certificate ca.cert.pem \ --load-ca-privkey ca.key.pem \ --template "$NAME".template \ --outfile "$NAME".cert.pem -fi chmod 600 "$NAME".cert.pem + +# vim: ft=sh diff --git a/tests/by-name/em/email-dns/nodes/acme/certs/output/acme.test.cert.pem b/tests/by-name/em/email-dns/nodes/acme/certs/output/acme.test.cert.pem new file mode 100644 index 0000000..687101d --- /dev/null +++ b/tests/by-name/em/email-dns/nodes/acme/certs/output/acme.test.cert.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBjTCCAT+gAwIBAgIUfiDKld3eiPKuFhsaiHpPNmbMJU8wBQYDK2VwMCoxCzAJ +BgNVBAYTAkVVMRswGQYDVQQKExJWaGFjay5ldSBUZXN0IEtleXMwIBcNMjUwMzAx +MTEyNjU2WhgPMjM2MzAzMDYxMTI2NTZaMB0xGzAZBgNVBAoTElZoYWNrLmV1IFRl +c3QgS2V5czAqMAUGAytlcAMhAHYq2cjrfrlslWxvcKjs2cD7THbpmtq+jf/dlrKW +UEo8o4GBMH8wDAYDVR0TAQH/BAIwADAfBgNVHREEGDAWgglhY21lLnRlc3SCCWFj +bWUudGVzdDAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFN/1UyS0jnC3LoryMIL2 +/6cdsYBBMB8GA1UdIwQYMBaAFLUZcL/zguHlulHg5GYyYhXmVt/6MAUGAytlcANB +ALz3u7lBreHeVZ0YXrwK3SDwlhWIH/SeUQwbxQlarzR47qu3cwQQ93Y1xjtOdu+h +hOM/ig3nLGVOT6qL8IsZrQk= +-----END CERTIFICATE----- diff --git a/tests/by-name/em/email-dns/nodes/acme/certs/output/acme.test.key.pem b/tests/by-name/em/email-dns/nodes/acme/certs/output/acme.test.key.pem new file mode 100644 index 0000000..06195b8 --- /dev/null +++ b/tests/by-name/em/email-dns/nodes/acme/certs/output/acme.test.key.pem @@ -0,0 +1,25 @@ +Public Key Info: + Public Key Algorithm: EdDSA (Ed25519) + Key Security Level: High (256 bits) + +curve: Ed25519 +private key: + 9d:25:38:89:f2:37:d7:65:41:f5:24:ba:4c:19:fb:0f + 86:c8:a3:cf:f7:08:57:69:cc:64:cf:55:2d:8e:99:3e + + +x: + 76:2a:d9:c8:eb:7e:b9:6c:95:6c:6f:70:a8:ec:d9:c0 + fb:4c:76:e9:9a:da:be:8d:ff:dd:96:b2:96:50:4a:3c + + + +Public Key PIN: + pin-sha256:NPwZitkDv4isUmdiicSsM1t1OtYoxqhdvBUnqSc4bFQ= +Public Key ID: + sha256:34fc198ad903bf88ac52676289c4ac335b753ad628c6a85dbc1527a927386c54 + sha1:dff55324b48e70b72e8af23082f6ffa71db18041 + +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIJ0lOInyN9dlQfUkukwZ+w+GyKPP9whXacxkz1Utjpk+ +-----END PRIVATE KEY----- diff --git a/tests/by-name/em/email-dns/nodes/acme/certs/output/acme.test.template b/tests/by-name/em/email-dns/nodes/acme/certs/output/acme.test.template new file mode 100644 index 0000000..320a170 --- /dev/null +++ b/tests/by-name/em/email-dns/nodes/acme/certs/output/acme.test.template @@ -0,0 +1,5 @@ +dns_name = "acme.test" +dns_name = "acme.test" +expiration_days = 123456 +organization = Vhack.eu Test Keys +encryption_key diff --git a/tests/by-name/em/email-dns/nodes/acme/certs/output/ca.cert.pem b/tests/by-name/em/email-dns/nodes/acme/certs/output/ca.cert.pem new file mode 100644 index 0000000..0fa9d14 --- /dev/null +++ b/tests/by-name/em/email-dns/nodes/acme/certs/output/ca.cert.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBYDCCARKgAwIBAgIUdhVVcf+NgElqGuutU55FUDBtFVMwBQYDK2VwMCoxCzAJ +BgNVBAYTAkVVMRswGQYDVQQKExJWaGFjay5ldSBUZXN0IEtleXMwIBcNMjUwMzAx +MTEyNjU2WhgPMjM2MzAzMDYxMTI2NTZaMCoxCzAJBgNVBAYTAkVVMRswGQYDVQQK +ExJWaGFjay5ldSBUZXN0IEtleXMwKjAFBgMrZXADIQCkO1LhHINvJjt41JD6UEc4 +ZKKUubB8lKPxSOyTkFBOgqNIMEYwDwYDVR0TAQH/BAUwAwEB/zAUBgNVHREEDTAL +gglhY21lLnRlc3QwHQYDVR0OBBYEFLUZcL/zguHlulHg5GYyYhXmVt/6MAUGAytl +cANBAFMFFy5tjuQtp5GVEN6qM50L4lteQuxfhlQqmOOfl06HV6153wJnrlKaTOYO +t0dKlSqKROMYUYeU39xDp07MLAc= +-----END CERTIFICATE----- diff --git a/tests/by-name/em/email-dns/nodes/acme/certs/output/ca.key.pem b/tests/by-name/em/email-dns/nodes/acme/certs/output/ca.key.pem new file mode 100644 index 0000000..64263bc --- /dev/null +++ b/tests/by-name/em/email-dns/nodes/acme/certs/output/ca.key.pem @@ -0,0 +1,25 @@ +Public Key Info: + Public Key Algorithm: EdDSA (Ed25519) + Key Security Level: High (256 bits) + +curve: Ed25519 +private key: + 82:0d:fc:f0:d6:82:89:63:e5:bc:23:78:ba:98:38:83 + 09:2d:e0:78:4c:53:92:e3:db:5b:2f:e4:39:ce:96:3d + + +x: + a4:3b:52:e1:1c:83:6f:26:3b:78:d4:90:fa:50:47:38 + 64:a2:94:b9:b0:7c:94:a3:f1:48:ec:93:90:50:4e:82 + + + +Public Key PIN: + pin-sha256:jpzYZMOHDPCeSXxfL+YUXgSPcbO9MAs8foGMP5CJiD8= +Public Key ID: + sha256:8e9cd864c3870cf09e497c5f2fe6145e048f71b3bd300b3c7e818c3f9089883f + sha1:b51970bff382e1e5ba51e0e466326215e656dffa + +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIIIN/PDWgolj5bwjeLqYOIMJLeB4TFOS49tbL+Q5zpY9 +-----END PRIVATE KEY----- diff --git a/tests/by-name/em/email-dns/nodes/acme/certs/output/ca.template b/tests/by-name/em/email-dns/nodes/acme/certs/output/ca.template new file mode 100644 index 0000000..a2295d8 --- /dev/null +++ b/tests/by-name/em/email-dns/nodes/acme/certs/output/ca.template @@ -0,0 +1,5 @@ +country = EU +dns_name = "acme.test" +expiration_days = 123456 +organization = Vhack.eu Test Keys +ca diff --git a/tests/by-name/em/email-dns/nodes/acme/certs/snakeoil-certs.nix b/tests/by-name/em/email-dns/nodes/acme/certs/snakeoil-certs.nix new file mode 100644 index 0000000..aeb6dfc --- /dev/null +++ b/tests/by-name/em/email-dns/nodes/acme/certs/snakeoil-certs.nix @@ -0,0 +1,13 @@ +let + domain = "acme.test"; +in { + inherit domain; + ca = { + cert = ./output/ca.cert.pem; + key = ./output/ca.key.pem; + }; + "${domain}" = { + cert = ./output/. + "/${domain}.cert.pem"; + key = ./output/. + "/${domain}.key.pem"; + }; +} diff --git a/tests/by-name/em/email-dns/nodes/acme/client.nix b/tests/by-name/em/email-dns/nodes/acme/client.nix new file mode 100644 index 0000000..2b870e8 --- /dev/null +++ b/tests/by-name/em/email-dns/nodes/acme/client.nix @@ -0,0 +1,21 @@ +{ + nodes, + lib, + ... +}: let + inherit (nodes.acme.test-support.acme) caCert; + inherit (nodes.acme.test-support.acme) caDomain; +in { + security = { + acme = { + acceptTerms = true; + defaults = { + server = "https://${caDomain}/dir"; + }; + }; + + pki = { + certificateFiles = lib.mkForce [caCert]; + }; + }; +} diff --git a/tests/by-name/em/email-dns/nodes/acme/default.nix b/tests/by-name/em/email-dns/nodes/acme/default.nix new file mode 100644 index 0000000..236ba6a --- /dev/null +++ b/tests/by-name/em/email-dns/nodes/acme/default.nix @@ -0,0 +1,114 @@ +# The certificate for the ACME service is exported as: +# +# config.test-support.acme.caCert +# +# This value can be used inside the configuration of other test nodes to inject +# the test certificate into security.pki.certificateFiles or into package +# overlays. +# +# { +# acme = { nodes, lib, ... }: { +# imports = [ ./common/acme/server ]; +# networking.nameservers = lib.mkForce [ +# nodes.mydnsresolver.networking.primaryIPAddress +# ]; +# }; +# +# dnsmyresolver = ...; +# } +# +# Keep in mind, that currently only _one_ resolver is supported, if you have +# more than one resolver in networking.nameservers only the first one will be +# used. +# +# Also make sure that whenever you use a resolver from a different test node +# that it has to be started _before_ the ACME service. +{ + config, + pkgs, + lib, + ... +}: let + testCerts = import ./certs/snakeoil-certs.nix; + inherit (testCerts) domain; + + pebbleConf.pebble = { + listenAddress = "0.0.0.0:443"; + managementListenAddress = "0.0.0.0:15000"; + + # The cert and key are used only for the Web Front End (WFE) + certificate = testCerts.${domain}.cert; + privateKey = testCerts.${domain}.key; + + httpPort = 80; + tlsPort = 443; + ocspResponderURL = "http://${domain}:4002"; + strict = true; + }; + + pebbleConfFile = pkgs.writeText "pebble.conf" (builtins.toJSON pebbleConf); +in { + options.test-support.acme = { + caDomain = lib.mkOption { + type = lib.types.str; + default = domain; + readOnly = true; + description = '' + A domain name to use with the `nodes` attribute to + identify the CA server in the `client` config. + ''; + }; + caCert = lib.mkOption { + type = lib.types.path; + readOnly = true; + default = testCerts.ca.cert; + description = '' + A certificate file to use with the `nodes` attribute to + inject the test CA certificate used in the ACME server into + {option}`security.pki.certificateFiles`. + ''; + }; + }; + + config = { + networking = { + # This has priority 140, because modules/testing/test-instrumentation.nix + # already overrides this with priority 150. + nameservers = lib.mkOverride 140 ["127.0.0.1"]; + firewall.allowedTCPPorts = [ + 80 + 443 + 15000 + 4002 + ]; + + extraHosts = '' + 127.0.0.1 ${domain} + ${config.networking.primaryIPAddress} ${domain} + ''; + }; + + systemd.services = { + pebble = { + enable = true; + description = "Pebble ACME server"; + wantedBy = ["network.target"]; + environment = { + # We're not testing lego, we're just testing our configuration. + # No need to sleep. + PEBBLE_VA_NOSLEEP = "1"; + }; + + serviceConfig = { + RuntimeDirectory = "pebble"; + WorkingDirectory = "/run/pebble"; + + # Required to bind on privileged ports. + AmbientCapabilities = ["CAP_NET_BIND_SERVICE"]; + + ExecStart = "${pkgs.pebble}/bin/pebble -config ${pebbleConfFile}"; + }; + }; + }; + }; +} diff --git a/tests/by-name/em/email-dns/nodes/mail_server.nix b/tests/by-name/em/email-dns/nodes/mail_server.nix new file mode 100644 index 0000000..a8c528a --- /dev/null +++ b/tests/by-name/em/email-dns/nodes/mail_server.nix @@ -0,0 +1,56 @@ +{ + extraModules, + pkgs, + vhackPackages, +}: { + mkMailServer = serverName: principal: { + config, + lib, + nodes, + ... + }: { + imports = + extraModules + ++ [ + ../../../../../modules + ./acme/client.nix + ]; + + environment.systemPackages = [ + pkgs.bind + pkgs.openssl + ]; + + networking.nameservers = lib.mkForce [ + nodes.name_server.networking.primaryIPAddress + nodes.name_server.networking.primaryIPv6Address + ]; + + age.identityPaths = ["${../secrets/hostKey}"]; + + vhack = { + stalwart-mail = { + enable = true; + fqdn = "${serverName}.server.com"; + admin = "admin@${serverName}.server.com"; + security = { + dkimKeys = let + loadKey = name: { + dkimPublicKey = builtins.readFile (../secrets/dkim + "/${name}/public"); + dkimPrivateKeyPath = ../secrets/dkim + "/${name}/private.age"; + keyAlgorithm = "ed25519-sha256"; + }; + in { + "mail1.server.com" = loadKey "mail1.server.com"; + "mail2.server.com" = loadKey "mail2.server.com"; + "alice.com" = loadKey "alice.com"; + "bob.com" = loadKey "bob.com"; + }; + verificationMode = "strict"; + }; + openFirewall = true; + principals = [principal]; + }; + }; + }; +} diff --git a/tests/by-name/em/email-dns/nodes/name_server.nix b/tests/by-name/em/email-dns/nodes/name_server.nix new file mode 100644 index 0000000..ef657f4 --- /dev/null +++ b/tests/by-name/em/email-dns/nodes/name_server.nix @@ -0,0 +1,320 @@ +{extraModules}: { + config, + lib, + nodes, + pkgs, + ... +}: let + keyAlgoToKeyType = keyAlgo: + if keyAlgo == "ed25519-sha256" + then "ed25519" + else if keyAlgo == "rsa-sha-256" || keyAlgo == "rsa-sha-1" + then "rsa" + else builtins.throw "Impossible"; + + mkZone = user: nodes: lib: cfg: { + SOA = { + nameServer = "ns.server.com"; + adminEmail = "${user}@${user}.com"; + serial = 2024012301; + }; + + MX = [ + { + preference = 10; + exchange = "${cfg.fqdn}."; + } + ]; + + # https://www.rfc-editor.org/rfc/rfc8461.html#section-3.1 + # Also see the policy in the hmtl part. + MTA-STS = [ + { + id = "20250228Z"; + } + ]; + + # https://www.rfc-editor.org/rfc/rfc7208.html + # https://en.wikipedia.org/wiki/Sender_Policy_Framework + TXT = [ + (builtins.concatStringsSep " " + [ + "v=spf1" # The version. + "+mx" # Allow mail from this domain MX record. + "-all" # Reject all other emails if the previous mechanism did not match. + ]) + ]; + + # https://www.rfc-editor.org/rfc/rfc6376.html#section-3.6.1 + # https://www.rfc-editor.org/rfc/rfc6376.html#section-7.5 + DKIM = [ + { + selector = "mail"; + k = keyAlgoToKeyType cfg.security.dkimKeys."${user}.com".keyAlgorithm; + p = cfg.security.dkimKeys."${user}.com".dkimPublicKey; + s = ["email"]; + t = ["s"]; + } + ]; + + # https://www.rfc-editor.org/rfc/rfc7489.html#section-6.3 + DMARC = [ + { + adkim = "strict"; + aspf = "strict"; + fo = ["0" "1" "d" "s"]; + p = "quarantine"; + rua = cfg.admin; + ruf = [cfg.admin]; + } + ]; + + A = [ + nodes.${user}.networking.primaryIPAddress + ]; + AAAA = [ + nodes.${user}.networking.primaryIPv6Address + ]; + }; + mkServerZone = serverName: nodes: lib: let + cfg = nodes."${serverName}_server".vhack.stalwart-mail; + in { + SOA = { + nameServer = "ns.server.com"; + adminEmail = "admin@server.com"; + serial = 2024012301; + }; + MX = [ + { + preference = 10; + exchange = "${serverName}.server.com."; + } + ]; + + # https://www.rfc-editor.org/rfc/rfc6376.html#section-3.6.1 + # https://www.rfc-editor.org/rfc/rfc6376.html#section-7.5 + DKIM = [ + { + selector = "mail"; + k = keyAlgoToKeyType cfg.security.dkimKeys."${serverName}.server.com".keyAlgorithm; + p = cfg.security.dkimKeys."${serverName}.server.com".dkimPublicKey; + s = ["email"]; + t = ["s"]; + } + ]; + + # https://www.rfc-editor.org/rfc/rfc7489.html#section-6.3 + DMARC = [ + { + adkim = "strict"; + aspf = "strict"; + fo = ["0" "1" "d" "s"]; + p = "quarantine"; + rua = cfg.admin; + ruf = [cfg.admin]; + } + ]; + + # https://www.rfc-editor.org/rfc/rfc7208.html + # NOTE(@bpeetz): This server might not be directly sending mail, but it is still required for + # the SMTP EHLO check. <2025-02-25> + TXT = [ + (builtins.concatStringsSep " " + [ + "v=spf1" # The version. + "+mx" # Allow mail from this domain MX record. + "-all" # Reject all other emails if the previous mechanism did not match. + ]) + ]; + + A = [ + nodes."${serverName}_server".networking.primaryIPAddress + ]; + AAAA = [ + nodes."${serverName}_server".networking.primaryIPv6Address + ]; + }; +in { + imports = + extraModules + ++ [ + ../../../../../modules + ./acme/client.nix + ]; + + networking.nameservers = lib.mkForce [ + nodes.name_server.networking.primaryIPAddress + nodes.name_server.networking.primaryIPv6Address + ]; + + services.nginx = { + logError = "stderr debug"; + virtualHosts = let + mkStsHost = mx: { + forceSSL = true; + enableACME = true; + root = pkgs.runCommandLocal "mkPolicy" {} '' + mkdir --parents $out/.well-known/ + + # https://www.rfc-editor.org/rfc/rfc8461.html#section-3.2 + cat << EOF > $out/.well-known/mta-sts.txt + version: STSv1 + mode: enforce + mx: ${mx} + max_age: 604800 + EOF + ''; + }; + in { + "mta-sts.alice.com" = mkStsHost "mail2.server.com"; + "mta-sts.bob.com" = mkStsHost "mail1.server.com"; + }; + }; + + vhack = { + nginx = { + enable = true; + }; + dns = { + enable = true; + openFirewall = true; + interfaces = [ + nodes.name_server.networking.primaryIPAddress + nodes.name_server.networking.primaryIPv6Address + ]; + + zones = let + stsZone = { + SOA = { + nameServer = "ns"; + adminEmail = "admin@server.com"; + serial = 2025012301; + }; + + useOrigin = false; + + A = [ + nodes.name_server.networking.primaryIPAddress + ]; + AAAA = [ + nodes.name_server.networking.primaryIPv6Address + ]; + }; + in { + "arpa" = { + SOA = { + nameServer = "ns"; + adminEmail = "admin@server.com"; + serial = 2025012301; + }; + useOrigin = false; + + PTR = [ + { + name = "acme.test"; + ip.v4 = nodes.acme.networking.primaryIPAddress; + } + { + name = "acme.test"; + ip.v6 = nodes.acme.networking.primaryIPv6Address; + } + + { + name = "alice.com"; + ip.v4 = nodes.alice.networking.primaryIPAddress; + } + { + name = "alice.com"; + ip.v6 = nodes.alice.networking.primaryIPv6Address; + } + + { + name = "bob"; + ip.v4 = nodes.bob.networking.primaryIPAddress; + } + { + name = "bob"; + ip.v6 = nodes.bob.networking.primaryIPv6Address; + } + + { + name = "mail1.server.com"; + ip.v4 = nodes.mail1_server.networking.primaryIPAddress; + } + { + name = "mail1.server.com"; + ip.v6 = nodes.mail1_server.networking.primaryIPv6Address; + } + + { + name = "mail2.server.com"; + ip.v4 = nodes.mail2_server.networking.primaryIPAddress; + } + { + name = "mail2.server.com"; + ip.v6 = nodes.mail2_server.networking.primaryIPv6Address; + } + + { + name = "ns.server.com"; + ip.v4 = nodes.name_server.networking.primaryIPAddress; + } + { + name = "ns.server.com"; + ip.v6 = nodes.name_server.networking.primaryIPv6Address; + } + ]; + }; + + "alice.com" = mkZone "alice" nodes lib nodes.mail2_server.vhack.stalwart-mail; + "mta-sts.alice.com" = stsZone; + "bob.com" = mkZone "bob" nodes lib nodes.mail1_server.vhack.stalwart-mail; + "mta-sts.bob.com" = stsZone; + "mail1.server.com" = mkServerZone "mail1" nodes lib; + "mail2.server.com" = mkServerZone "mail2" nodes lib; + "ns.server.com" = { + SOA = { + nameServer = "ns"; + adminEmail = "admin@server.com"; + serial = 2025012301; + }; + useOrigin = false; + + A = [ + nodes.name_server.networking.primaryIPAddress + ]; + AAAA = [ + nodes.name_server.networking.primaryIPv6Address + ]; + }; + "acme.test" = { + SOA = { + nameServer = "ns"; + adminEmail = "admin@server.com"; + serial = 2025012301; + }; + useOrigin = false; + + A = [ + nodes.acme.networking.primaryIPAddress + ]; + AAAA = [ + nodes.acme.networking.primaryIPv6Address + ]; + }; + "server.com" = { + SOA = { + nameServer = "ns"; + adminEmail = "admin@server.com"; + serial = 2025012301; + }; + + useOrigin = false; + NS = [ + "ns.server.com." + ]; + }; + }; + }; + }; +} diff --git a/tests/by-name/em/email-dns/nodes/user.nix b/tests/by-name/em/email-dns/nodes/user.nix new file mode 100644 index 0000000..e4db347 --- /dev/null +++ b/tests/by-name/em/email-dns/nodes/user.nix @@ -0,0 +1,74 @@ +{ + pkgs, + vhackPackages, +}: { + mkUser = user: serverName: { + nodes, + lib, + ... + }: { + imports = [ + ./acme/client.nix + ]; + + environment.systemPackages = [ + vhackPackages.fetchmail-common-name + pkgs.msmtp + pkgs.procmail + + pkgs.bind + pkgs.openssl + ]; + + networking.nameservers = lib.mkForce [ + nodes.name_server.networking.primaryIPAddress + nodes.name_server.networking.primaryIPv6Address + ]; + + users.users."${user}" = {isNormalUser = true;}; + + systemd.tmpfiles.rules = [ + "d /home/${user}/mail 0700 ${user} users - -" + "L /home/${user}/.fetchmailrc - - - - /etc/homeSetup/.fetchmailrc" + "L /home/${user}/.procmailrc - - - - /etc/homeSetup/.procmailrc" + "L /home/${user}/.msmtprc - - - - /etc/homeSetup/.msmtprc" + ]; + + environment.etc = { + "homeSetup/.fetchmailrc" = { + text = '' + poll "${serverName}.server.com" protocol IMAP + username "${user}" + password "${user}-password" + ssl + mda procmail; + ''; + mode = "0600"; + inherit user; + }; + "homeSetup/.procmailrc" = { + text = '' + DEFAULT=$HOME/mail + ''; + mode = "0600"; + inherit user; + }; + "homeSetup/.msmtprc" = { + text = '' + account ${user} + host ${serverName}.server.com + domain ${user}.com + port 465 + from ${user}@${user}.com + user ${user} + password ${user}-password + auth on + tls on + tls_starttls off + ''; + mode = "0600"; + inherit user; + }; + }; + }; +} diff --git a/tests/by-name/em/email-dns/secrets/dkim/alice.com/private.age b/tests/by-name/em/email-dns/secrets/dkim/alice.com/private.age new file mode 100644 index 0000000..5415fdc --- /dev/null +++ b/tests/by-name/em/email-dns/secrets/dkim/alice.com/private.age @@ -0,0 +1,15 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3dkJqTFNjOUphUWpBTmUv +MldLUUVtUWJWY2p4NGNDVlluWUdJNUVGTDBnCkRMZ3VsVGkvUUJsbUgrUHA5d1pY +d3lvQkdsSnRxYWxSbGlkQUlnMEFiWncKLT4gWDI1NTE5IGljWDF2a3JqZHBzZC9J +ZjlxSVdKQ0ErRGc1VVRTNE91RC8rR2ZhMlN5d28KYVNyMWZWSnZFNS9zWUdLS01T +UkJLaGxqWmwySzdxRk5GVUZ4NFR1aXcxcwotPiBzc2gtZWQyNTUxOSBSc2dXcFEg +alJlckZsT21YUUQrS05Xb25TeFJXUmFFTFl1U3NYOXZoWEZwa0Qrd2dnWQoxZVla +TFV3VURsaUxZdUZVRkZhdXcyV3AzMTJCc1hXUDJlK2FPc0pIU2hvCi0+IFN6Nlc4 +R0dqLWdyZWFzZSBfIQpUdERFc2xwZGkwR2p3QW0vNlIyR0pYTkkyeU02ckw2RnEv +bHE5TFQ4TlBaL3lNd1ZFbDRPZXo1RmpSeStWcjljCmFpV28KLS0tIEE2SURxTHdl +RWt5NXZRT3hFUHpjSm1BdTlwZGM2NmQvNm5Pb0s4dW5pc2sKTKoHvkHEkuPkMTDw +Un+0mSGY82ZxfpNuYH6YEjUYsXwAw4y0HFvsObc0XuYzCmQrBX8vLos0Sg5kzboT +EU81RdjI1AQPwhd+jBIDNFpUzYZ/IaQsz7Yp3G1WDfkeYPk10IuzOJoeRjLC9HPa +wfYCKZDhegV6n//kAxBGM0Q3MzyxO3eYJ+k3TaWnUPLnw+nFMAANgkYqfg== +-----END AGE ENCRYPTED FILE----- diff --git a/tests/by-name/em/email-dns/secrets/dkim/alice.com/public b/tests/by-name/em/email-dns/secrets/dkim/alice.com/public new file mode 100644 index 0000000..0f3c3b2 --- /dev/null +++ b/tests/by-name/em/email-dns/secrets/dkim/alice.com/public @@ -0,0 +1 @@ +cLWzd3zg51ITME1Fnu16/h07lXIUxfhdLivktUMoVQs= \ No newline at end of file diff --git a/tests/by-name/em/email-dns/secrets/dkim/bob.com/private.age b/tests/by-name/em/email-dns/secrets/dkim/bob.com/private.age new file mode 100644 index 0000000..c07c997 --- /dev/null +++ b/tests/by-name/em/email-dns/secrets/dkim/bob.com/private.age @@ -0,0 +1,14 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmN0FHQ2ZhVEVpNnB1S2Zu +T3JBS0xLUklKNUxKeENYeis3MUZHdUF1STFFCmdkTFQvcG9qWk55QjlEUDN2NGFa +Ump1TW1xRUZ3K251bFJsaEMrTXd3VzgKLT4gWDI1NTE5IFF6ZGNJbWkrc1Z5M3Rk +blpNUGpuNU0rZi9Ed2dicnM4WXhrbjdQVDFzeGsKd2V6clA0UjZWYmJxZytaa3B3 +T2drNkhiQVBKU2tOL2gxeGtjQnAra2NxawotPiBzc2gtZWQyNTUxOSBSc2dXcFEg +Y2xrRDduWXo1Qk81VFMxMHZ0T3FhaXZXUm1VMDNhVGZscEJ6Q2owWXFCMApsMTQ4 +K2hGNm4ybGNkSTFOeHdwZnI5YjRZQm1uVC9xUEtRSjBxV0JKdERRCi0+IFlzLWdy +ZWFzZSBbZztrCk1sWFhaZXNZOUhMbAotLS0gbkNRK2hkZ0dEZEkxeFVJKysyUUY3 +UWVBRjZoVXJCWk5YTEh4ZktpM1IwNArrxTAoCB+Ubb79nB53ZnbakCaDfrfLx7Oi +Khq4CZh64r2LaQWFP0m5dhHWDF/8Fg+E42zbXN1KQwggz2h59EqI2ouOXmjiZeHN +O50mfruF9xybdAFVIThEsjRTRQDPfO5qq6PI+g+w4s+S8kl6yp61t6z3y+zI3GGw +RxCsY2uEn5naMuBMqkYL5dhA/G5deNMpUQ8rcRZw +-----END AGE ENCRYPTED FILE----- diff --git a/tests/by-name/em/email-dns/secrets/dkim/bob.com/public b/tests/by-name/em/email-dns/secrets/dkim/bob.com/public new file mode 100644 index 0000000..ddea670 --- /dev/null +++ b/tests/by-name/em/email-dns/secrets/dkim/bob.com/public @@ -0,0 +1 @@ +3yrKD52yd5hBA6ue5uQVl7FXGK8UOlUE9Y+yCdBRfVQ= \ No newline at end of file diff --git a/tests/by-name/em/email-dns/secrets/dkim/gen_key.sh b/tests/by-name/em/email-dns/secrets/dkim/gen_key.sh new file mode 100755 index 0000000..48b4434 --- /dev/null +++ b/tests/by-name/em/email-dns/secrets/dkim/gen_key.sh @@ -0,0 +1,35 @@ +#! /usr/bin/env nix-shell +#! nix-shell -p rage -p openssl -p dash -i dash --impure + +# shellcheck shell=dash + +cd "$(dirname "$0")" || { + echo "No basedir?!" + exit 1 +} + +key_name="$1" +[ -z "$key_name" ] && { + echo "Usage: $0 KEY_NAME" + exit 2 +} + +[ -d "$key_name" ] || mkdir "$key_name" +cd "$key_name" || { + echo "Just created." + exit 1 +} + +openssl genpkey -algorithm ed25519 -out "private" +openssl pkey -in "private" -pubout -out "public.tmp" + +openssl asn1parse -in "public.tmp" -offset 12 -noout -out /dev/stdout | base64 --wrap 0 >"public" +rm "public.tmp" + +rage --encrypt \ + --armor \ + --recipient "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILxdvBk/PC9fC7B5vqe9TvygZKY6LgDQ2mXRdVrthBM/" \ + "private" >"private.age" +rm "private" + +# vim: ft=sh diff --git a/tests/by-name/em/email-dns/secrets/dkim/mail1.server.com/private.age b/tests/by-name/em/email-dns/secrets/dkim/mail1.server.com/private.age new file mode 100644 index 0000000..8c5d3c3 --- /dev/null +++ b/tests/by-name/em/email-dns/secrets/dkim/mail1.server.com/private.age @@ -0,0 +1,15 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOYXRiK29XallqZG1vc1Bl +TlpFbk9WZmZIK1FrUHRIbmx6bEI5UkhJZUZ3Ck8zcHVGWHJjdG5wZnh0SDljc2F5 +WU1DMUNxdFdVQmRUMFNpa0VCSUkxZmsKLT4gWDI1NTE5IFhXREJyTVNueGp6YVg1 +b3FUemlodUMraWRMVnEyQTZqRlpKanhxdDBSMlkKYVNxcXFockpleGJBaU1iUUQ4 +bml2azlPSE9Jc0N0dDU2d1hYOXFpcnV2ZwotPiBzc2gtZWQyNTUxOSBSc2dXcFEg +ZUVuOHZyM3pRcTZHKzFsV2VheDkrdml4NUVIWldQY1BqcGNpTG5lU2VrVQoydnVZ +UHdpa1FmT3hNbHA1dll1dVFxS0VFanV3N3Y5Z1E5ejBiMkhRSHhRCi0+ICVuPzx1 +Ky1ncmVhc2UgYXY5PE43Om4KcWplaklGSXlMSk0yNktld21ucUdVS1dFVmt4NFhi +VG5WYUNMUGdxZ0dta1F6RVpRRTdoek1WT0hwaC9CVEcyMworUQotLS0genJEN2Jq +Y1JVbzc3NVFoSXAxSUxEQUZzZkRCcXNmSXBwZDB0eDMybE5NMApv3ghQF9tC00yI +v7Sa9ZKrA8HDb/wpUn0X+D+ShLC95rW8+oPonN6gt+z+PoVUFvXwKsP/1I+D6z+B +PRMurlvpspkIGlgi5S5H6brj9UJ7Pt61+Ld2/gaLevzCPy1QCmlAlqRKvZuZMUUR +hWbmEXi8j56ClcxpVe62p+4GCI41T2cjAogi2C33dtviIcGedq9byD5tbVt4 +-----END AGE ENCRYPTED FILE----- diff --git a/tests/by-name/em/email-dns/secrets/dkim/mail1.server.com/public b/tests/by-name/em/email-dns/secrets/dkim/mail1.server.com/public new file mode 100644 index 0000000..4941b85 --- /dev/null +++ b/tests/by-name/em/email-dns/secrets/dkim/mail1.server.com/public @@ -0,0 +1 @@ +quDd9+ogqiIUWybfegosFFkG7jAsblij2VrkuUXEzzY= \ No newline at end of file diff --git a/tests/by-name/em/email-dns/secrets/dkim/mail2.server.com/private.age b/tests/by-name/em/email-dns/secrets/dkim/mail2.server.com/private.age new file mode 100644 index 0000000..d39631a --- /dev/null +++ b/tests/by-name/em/email-dns/secrets/dkim/mail2.server.com/private.age @@ -0,0 +1,15 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCRkl1SThtVnN3bUg4cmJL +aXBBUU4yRE1DOGdYclg1UXVhS1Y0UTJPVVIwCjlSOXZJdVB0R0VxbGdUWHd5U2RO +RGpFdUw4c3EyUFZNZUx4Mm5yeGNIeVUKLT4gWDI1NTE5IHc5K05EeE1xNnBHd1dS +WDJwSTdzLys3MnRncmN0WWZjTWkvM0tqeWRRU1EKek13RGRsNUhadncxQ3V6ZFcv +Ni9aMTFtRllFK0lvd2tidnRwUjE0V0d2OAotPiBzc2gtZWQyNTUxOSBSc2dXcFEg +Mml5VEt5b3R3d09Gc1hzR2pRZzJFY3VHT0JwUnBaTTNIWG5QaGdsWkh4bwp1Nzdx +dWxpbjdNRGZ6aThxM0d6a2MraklkSW03ek15YXRkSXVyaS96QlFjCi0+IDAkRDEp +bC4tZ3JlYXNlCi9lUnduUFJreTdpMEU5ejZjS1JyWmJzRkZNRVdLbnRRZC81aldN +c2FqTTgrNnFCSEYxQUhSQWxyMW9icCtQYkgKTUEKLS0tIDNmQkwwbjJiSldraDVw +ZUs3SHNpU0pGWXRLSVBtZ0Y1QW5uT0N5bnpjek0KiujqGmiYB3hCso3u4uCtZuO3 +EmPPJkbHKPNyUQy1V/Bv+sjgPDQi/0y7UR759G91iNIYQB0fWg7njWsl8GNdJwXI +YO+2utDKkrm0DowWNKpHX1KE1g93e0H5ZoptygLFtx8TWDP2i8R7JJqJ9QevSz6r +Nwjj+d3HGTxA7WvAZnNdvyfHbRRdvW+6q0uIjS5zHXAs8YmepA== +-----END AGE ENCRYPTED FILE----- diff --git a/tests/by-name/em/email-dns/secrets/dkim/mail2.server.com/public b/tests/by-name/em/email-dns/secrets/dkim/mail2.server.com/public new file mode 100644 index 0000000..5c4406d --- /dev/null +++ b/tests/by-name/em/email-dns/secrets/dkim/mail2.server.com/public @@ -0,0 +1 @@ +th9exwaYvoAjxW1tAj3k/VNLl5jKzSC/dxKrxM2mTZE= \ No newline at end of file diff --git a/tests/by-name/em/email-dns/secrets/hostKey b/tests/by-name/em/email-dns/secrets/hostKey new file mode 100644 index 0000000..79c9d6c --- /dev/null +++ b/tests/by-name/em/email-dns/secrets/hostKey @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACC8XbwZPzwvXwuweb6nvU78oGSmOi4A0Npl0XVa7YQTPwAAAJjFZPqHxWT6 +hwAAAAtzc2gtZWQyNTUxOQAAACC8XbwZPzwvXwuweb6nvU78oGSmOi4A0Npl0XVa7YQTPw +AAAEA9D5AP+Uqhrg8rPx2DjgucjfnJknkk7lkeKHMV04ZZv7xdvBk/PC9fC7B5vqe9Tvyg +ZKY6LgDQ2mXRdVrthBM/AAAAFSAnUHVibGljIHRlc3Rpbmcga2V5Jw== +-----END OPENSSH PRIVATE KEY----- diff --git a/tests/by-name/em/email-dns/test.nix b/tests/by-name/em/email-dns/test.nix new file mode 100644 index 0000000..32447ae --- /dev/null +++ b/tests/by-name/em/email-dns/test.nix @@ -0,0 +1,203 @@ +{ + nixos-lib, + pkgsUnstable, + nixpkgs-unstable, + vhackPackages, + pkgs, + extraModules, + nixLib, + ... +}: let + mail_server = import ./nodes/mail_server.nix {inherit extraModules pkgs vhackPackages;}; + inherit (mail_server) mkMailServer; + user = import ./nodes/user.nix {inherit pkgs vhackPackages;}; + inherit (user) mkUser; +in + nixos-lib.runTest { + hostPkgs = pkgs; # the Nixpkgs package set used outside the VMs + + name = "email-dns"; + + node = { + specialArgs = {inherit pkgsUnstable vhackPackages nixpkgs-unstable nixLib;}; + + # Use the nixpkgs as constructed by the `nixpkgs.*` options + pkgs = null; + }; + + nodes = { + acme = { + nodes, + lib, + ... + }: { + imports = [./nodes/acme]; + networking.nameservers = lib.mkForce [ + nodes.name_server.networking.primaryIPAddress + ]; + }; + + name_server = import ./nodes/name_server.nix {inherit extraModules;}; + + mail1_server = + mkMailServer "mail1" + { + class = "individual"; + name = "bob"; + secret = "bob-password"; + email = ["bob@bob.com"]; + }; + + mail2_server = + mkMailServer "mail2" + { + class = "individual"; + name = "alice"; + secret = "alice-password"; + email = ["alice@alice.com"]; + }; + + bob = mkUser "bob" "mail1"; + alice = mkUser "alice" "mail2"; + }; + + testScript = {...}: let + checkEmailEmpty = pkgs.writeShellScript "assert-empty-emails" '' + set -xe + + # fetchmail returns EXIT_CODE 1 when no new mail + fetchmail --verbose >&2 || [ "$?" -eq 1 ] || { + echo "Mail was not empty" >&2 + exit 1 + } + ''; + checkEmailNotEmpty = pkgs.writeShellScript "assert-empty-emails" '' + set -xe + + # fetchmail returns EXIT_CODE 1 when no new mail + fetchmail --verbose >&2 || [ "$?" -ne 1 ] || { + echo "No new mail" >&2 + exit 1 + } + ''; + checkSpamEmailNotEmpty = pkgs.writeShellScript "assert-empty-emails" '' + set -xe + + # fetchmail returns EXIT_CODE 1 when no new mail + fetchmail --folder JUNK --verbose >&2 || [ "$?" -ne 1 ] || { + echo "No new mail" >&2 + exit 1 + } + ''; + inherit (pkgs) lib; + in + /* + python + */ + '' + from time import sleep + + # Start dependencies for the other services + acme.start() + acme.wait_for_unit("pebble.service") + name_server.start() + name_server.wait_for_unit("nsd.service") + + # Start the actual testing machines + start_all() + + mail1_server.wait_for_unit("stalwart-mail.service") + mail1_server.wait_for_open_port(993) # imap + mail1_server.wait_for_open_port(465) # smtp + mail2_server.wait_for_unit("stalwart-mail.service") + mail2_server.wait_for_open_port(993) # imap + mail2_server.wait_for_open_port(465) # smtp + + alice.wait_for_unit("multi-user.target") + bob.wait_for_unit("multi-user.target") + + name_server.wait_until_succeeds("stat /var/lib/acme/mta-sts.alice.com/cert.pem") + name_server.wait_until_succeeds("stat /var/lib/acme/mta-sts.bob.com/cert.pem") + + with subtest("Add pebble ca key to all services"): + for node in [name_server, mail1_server, mail2_server, alice, bob]: + node.succeed("${pkgs.writeShellScript "fetch-and-set-ca" '' + set -xe + + # Fetch the randomly generated ca certificate + curl https://acme.test:15000/roots/0 > /tmp/ca.crt + curl https://acme.test:15000/intermediates/0 >> /tmp/ca.crt + + # Append it to the various system stores + # The file paths are from <nixpgks>/modules/security/ca.nix + for cert_path in "ssl/certs/ca-certificates.crt" "ssl/certs/ca-bundle.crt" "pki/tls/certs/ca-bundle.crt"; do + cert_path="/etc/$cert_path" + + mv "$cert_path" "$cert_path.old" + cat "$cert_path.old" > "$cert_path" + cat /tmp/ca.crt >> "$cert_path" + done + + export NIX_SSL_CERT_FILE=/tmp/ca.crt + export SSL_CERT_FILE=/tmp/ca.crt + + # TODO + # # P11-Kit trust source. + # environment.etc."ssl/trust-source".source = "$${cacertPackage.p11kit}/etc/ssl/trust-source"; + ''}") + + with subtest("Both mailserver successfully started all services"): + import json + def all_services_running(host): + (status, output) = host.systemctl("list-units --state=failed --plain --no-pager --output=json") + host_failed = json.loads(output) + assert len(host_failed) == 0, f"Expected zero failing services, but found: {json.dumps(host_failed, indent=4)}" + all_services_running(mail1_server) + all_services_running(mail2_server) + + with subtest("Both start without mail"): + alice.succeed("sudo -u alice ${checkEmailEmpty}") + bob.succeed("sudo -u bob ${checkEmailEmpty}") + + with subtest("Alice can send an empty email to bob"): + alice.succeed("sudo -u alice ${pkgs.writeShellScript "alice-send" '' + set -xe + + echo "" | msmtp --debug --account alice bob@bob.com >&2 + ''}") + + # Give `mail2_server` some time to send the email. + sleep(160) + + bob.succeed("sudo -u bob ${checkSpamEmailNotEmpty}") + + with subtest("Alice can send an non-empty email to bob"): + alice.succeed("sudo -u alice ${pkgs.writeShellScript "alice-send" '' + set -xe + + cat << EOF | msmtp --debug --account alice bob@bob.com >&2 + Subject: Hi bob, I'm Alice! + + Good day, Bob! + + This is an email. + It contains a subject and a body. + I also assert utf8 support by including my last name in this very message. + + XOXO + Alice van Dåligen. + + . + EOF + ''}") + + # Give `mail2_server` some time to send the email. + sleep(120) + + bob.succeed("sudo -u bob ${checkEmailNotEmpty}") + + mail1_server.copy_from_vm("/var/lib/", "server1") + mail2_server.copy_from_vm("/var/lib/", "server2") + bob.copy_from_vm("/home/bob/mail", "bob") + ''; + } diff --git a/tests/by-name/em/email-ip/test.nix b/tests/by-name/em/email-ip/test.nix new file mode 100644 index 0000000..688cd8f --- /dev/null +++ b/tests/by-name/em/email-ip/test.nix @@ -0,0 +1,174 @@ +{ + nixos-lib, + pkgsUnstable, + nixpkgs-unstable, + vhackPackages, + pkgs, + extraModules, + nixLib, + ... +}: let + domain = "mail.server.test"; + + scripts = { + checkEmailEmpty = pkgs.writeShellScript "assert-empty-emails" '' + set -xe + + # fetchmail returns EXIT_CODE 1 when no new mail + fetchmail --nosslcertck --verbose >&2 || [ "$?" -eq 1 ] || { + echo "Expected exit code 1." >&2 + exit 1 + } + ''; + }; + + mkUser = user: {nodes, ...}: let + domainIp = nodes.server.networking.primaryIPAddress; + in { + environment.systemPackages = with pkgs; [ + fetchmail + msmtp + procmail + ]; + + users.users."${user}" = {isNormalUser = true;}; + + systemd.tmpfiles.rules = [ + "d /home/${user}/mail 0700 ${user} users - -" + "L /home/${user}/.fetchmailrc - - - - /etc/homeSetup/.fetchmailrc" + "L /home/${user}/.procmailrc - - - - /etc/homeSetup/.procmailrc" + "L /home/${user}/.msmtprc - - - - /etc/homeSetup/.msmtprc" + ]; + + environment.etc = { + "homeSetup/.fetchmailrc" = { + text = '' + poll "${domainIp}" protocol IMAP + username "${user}" + password "${user}-password" + ssl + mda procmail; + ''; + mode = "0600"; + inherit user; + }; + "homeSetup/.procmailrc" = { + text = '' + DEFAULT=$HOME/mail + ''; + mode = "0600"; + inherit user; + }; + "homeSetup/.msmtprc" = { + text = '' + account ${user} + host ${domainIp} + port 465 + from ${user}@${domain} + user ${user} + password ${user}-password + auth on + tls on + tls_starttls off + ''; + mode = "0600"; + inherit user; + }; + }; + }; +in + nixos-lib.runTest { + hostPkgs = pkgs; # the Nixpkgs package set used outside the VMs + + name = "email"; + + node = { + specialArgs = {inherit pkgsUnstable vhackPackages nixpkgs-unstable nixLib;}; + + # Use the nixpkgs as constructed by the `nixpkgs.*` options + pkgs = null; + }; + + nodes = { + server = {config, ...}: { + imports = + extraModules + ++ [ + ../../../../modules + ]; + + vhack = { + nginx = { + enable = true; + selfsign = true; + }; + + stalwart-mail = { + enable = true; + fqdn = domain; + admin = "mailto:admin@${domain}"; + security = null; + openFirewall = true; + principals = [ + { + class = "individual"; + name = "alice"; + secret = "alice-password"; + email = ["alice@${domain}"]; + } + { + class = "individual"; + name = "bob"; + secret = "bob-password"; + email = ["bob@${domain}"]; + } + ]; + }; + }; + }; + + alice = mkUser "alice"; + bob = mkUser "bob"; + }; + + testScript = {...}: + /* + python + */ + '' + start_all() + + server.wait_for_unit("stalwart-mail.service") + server.wait_for_open_port(993) # imap + server.wait_for_open_port(465) # smtp + + with subtest("Both start without mail"): + alice.succeed("sudo -u alice ${scripts.checkEmailEmpty}") + bob.succeed("sudo -u bob ${scripts.checkEmailEmpty}") + + with subtest("Alice can send an email to bob"): + alice.succeed("sudo -u alice ${pkgs.writeShellScript "alice-send" '' + set -xe + + cat << EOF | msmtp --debug --account alice --tls-certcheck=off bob@${domain} >&2 + Hi Bob! + + This is an email. + It contains a subject and a body. + + ALICE + EOF + ''}") + bob.succeed("sudo -u bob ${pkgs.writeShellScript "bob-receive" '' + set -xe + + fetchmail --nosslcertck --verbose >&2 || { + echo New Mail did not arrive + exit 1 + } + ''}") + + server.copy_from_vm("/var/lib/", "server") + bob.copy_from_vm("/home/bob/mail", "bob") + ''; + } diff --git a/zones/default.nix b/zones/default.nix new file mode 100644 index 0000000..babb531 --- /dev/null +++ b/zones/default.nix @@ -0,0 +1,3 @@ +{lib, ...}: { + "vhack.eu" = import ./vhack.eu/zone.nix {inherit lib;}; +} diff --git a/zones/vhack.eu/zone.nix b/zones/vhack.eu/zone.nix new file mode 100644 index 0000000..a1cb634 --- /dev/null +++ b/zones/vhack.eu/zone.nix @@ -0,0 +1,187 @@ +{lib, ...}: let + /* + Computes a reasonable value for the DNS serial number from the date of last change + and the iteration of that day. + + # Type + + mkSerial :: Number -> Number -> Number -> Number -> Number + + # Arguments + + year + : The year of the last change in the format YYYY. + + month + : The month of the last change in the format MM. + + day + : The day of the last change in the format DD. + + iteration + : The number of change on that day. The format should be CC (assuming there are less + than 100 changes happening on a day) . + + # Examples + + mkSerial 2025 04 01 01 + => 2025040101 + */ + mkSerial = year: month: day: iteration: let + n2 = lib.strings.fixedWidthNumber 2; + n4 = lib.strings.fixedWidthNumber 4; + in + lib.strings.toIntBase10 "${n4 year}${n2 month}${n2 day}${n2 iteration}"; +in { + SOA = { + nameServer = "name-server1.vhack.eu."; + adminEmail = "dns-admin@foss-syndicate.org"; + # NOTE(@bpeetz): ALWAYS change the serial number, when you change something in the + # zone file! <2025-04-01> + serial = mkSerial 2025 04 07 01; + }; + useOrigin = false; + + # NOTE: matrix/mastodon need to have the point from `vhack.eu` to their IP <2025-03-10> + A = [ + "92.60.38.179" + ]; + AAAA = [ + "2a03:4000:33:25b::4f4e" + ]; + + NS = [ + "name-server1.vhack.eu." + "name-server2.vhack.eu." + ]; + + CAA = [ + { + issuerCritical = false; + tag = "issue"; + value = "letsencrypt.org"; + } + ]; + + # Mail section {{{ + MX = [ + { + preference = 10; + exchange = "mail.vhack.eu."; + } + { + preference = 100; + exchange = "mail.foss-syndicate.org."; + } + ]; + + # https://www.rfc-editor.org/rfc/rfc7208.html + TXT = [ + (builtins.concatStringsSep " " + [ + "v=spf1" # The version. + "+mx" # Allow mail from this domain MX record. + "-all" # Reject all other emails if the previous mechanism did not match. + ]) + ]; + + # https://www.rfc-editor.org/rfc/rfc6376.html#section-3.6.1 + # https://www.rfc-editor.org/rfc/rfc6376.html#section-7.5 + DKIM = [ + { + selector = "mail"; + k = "ed25519"; + p = "U0eOxgLD3yK7PKzQRSZdJ3EH/UwVxPeYmfm42gYXsDg="; + s = ["email"]; + t = ["s"]; + } + ]; + + # https://www.rfc-editor.org/rfc/rfc7489.html#section-6.3 + DMARC = [ + { + adkim = "strict"; + aspf = "strict"; + fo = ["0" "1" "d" "s"]; + p = "reject"; + rua = "admin@foss-syndicate.org"; + ruf = ["admin@foss-syndicate.org"]; + } + ]; + + # https://www.rfc-editor.org/rfc/rfc2782.txt + SRV = [ + { + service = "imaps"; + proto = "tcp"; + priority = 0; + weight = 1; + port = 993; + target = "mail.vhack.eu."; + } + { + service = "pop3s"; + proto = "tcp"; + priority = 0; + weight = 1; + port = 995; + target = "mail.vhack.eu."; + } + { + service = "smtps"; + proto = "tcp"; + priority = 0; + weight = 1; + port = 465; + target = "mail.vhack.eu."; + } + ]; + # }}} + + subdomains = { + name-server1.CNAME = ["server2.vhack.eu."]; + name-server2.CNAME = ["server3.vhack.eu."]; + + source.CNAME = ["server2.vhack.eu."]; + + mail.CNAME = ["server3.vhack.eu."]; + + dav.CNAME = ["server2.vhack.eu."]; + etebase.CNAME = ["server2.vhack.eu."]; + git.CNAME = ["server2.vhack.eu."]; + invidious-router.CNAME = ["server2.vhack.eu."]; + + libreddit.CNAME = ["server2.vhack.eu."]; + redlib.CNAME = ["server2.vhack.eu."]; + + nextcloud.CNAME = ["server2.vhack.eu."]; + + mastodon.CNAME = ["server3.vhack.eu."]; + matrix.CNAME = ["server3.vhack.eu."]; + + miniflux.CNAME = ["server3.vhack.eu."]; + rss.CNAME = ["server3.vhack.eu."]; + + mumble.CNAME = ["server3.vhack.eu."]; + openpgpkey.CNAME = ["server3.vhack.eu."]; + peertube.CNAME = ["server3.vhack.eu."]; + trinitrix.CNAME = ["server3.vhack.eu."]; + + server2 = { + AAAA = [ + "2a03:4000:a:106::1" + ]; + A = [ + "185.16.61.132" + ]; + }; + server3 = { + AAAA = [ + "2a03:4000:33:25b::4f4e" + ]; + A = [ + "92.60.38.179" + ]; + }; + }; +} |