diff options
105 files changed, 6662 insertions, 1787 deletions
diff --git a/.envrc b/.envrc index 189401d..71bc7ad 100644 --- a/.envrc +++ b/.envrc @@ -1,6 +1,9 @@ #! /usr/bin/env sh use flake + +PATH_add ./scripts/ + if on_git_branch; then echo && git status --short --branch fi diff --git a/flake.lock b/flake.lock index 19b236c..277b4f5 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": 1741148495, + "narHash": "sha256-EV8KUaIZ2/CdBXlutXrHoZYbWPeB65p5kKZk71gvDRI=", "owner": "ipetkov", "repo": "crane", - "rev": "fdd502f921936105869eba53db6593fc2a424c16", + "rev": "75390a36cd0c2cdd5f1aafd8a9f827d7107f2e53", "type": "github" }, "original": { @@ -111,11 +111,11 @@ ] }, "locked": { - "lastModified": 1734343412, - "narHash": "sha256-b7G8oFp0Nj01BYUJ6ENC9Qf/HsYAIZvN9k/p0Kg/PFU=", + "lastModified": 1740485968, + "narHash": "sha256-WK+PZHbfDjLyveXAxpnrfagiFgZWaTJglewBWniTn2Y=", "owner": "nix-community", "repo": "disko", - "rev": "a08bfe06b39e94eec98dd089a2c1b18af01fef19", + "rev": "19c1140419c4f1cdf88ad4c1cfb6605597628940", "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,11 +198,11 @@ }, "library": { "locked": { - "lastModified": 1738161079, - "narHash": "sha256-YDiu0DbtwOSo1GO3D9A0Q+dHzsYJ6pJ8ZYEkgmK1szI=", + "lastModified": 1738443114, + "narHash": "sha256-IV7n/l3rFoz5UuavrDv0a7IIOPne0jDQVmJAR8bve8U=", "ref": "prime", - "rev": "847a8167fe3b52c3b8e19017b31a97e12ad411ea", - "revCount": 16, + "rev": "65bf71bb6ef05ce684924a1dc248bb2e8e2869fb", + "revCount": 17, "type": "git", "url": "https://git.foss-syndicate.org/vhack.eu/nix-library" }, @@ -214,11 +214,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1734522913, - "narHash": "sha256-tyReZKZRdyODkbcwYnO7xowXx7VCFJ6XzAY7w2aFjs0=", + "lastModified": 1741318725, + "narHash": "sha256-3ShROHs7BXBDH3VNoPmbG4mL8DvRpDM8s4NxkmRVz1Q=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "bf383789c497270e8e20ccc2261cf2c6e18dbda8", + "rev": "9290fda826610430b3fc8cc98443c3a2faaaf151", "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": 1741323510, + "narHash": "sha256-zQL0iErtVTxywxyWc7ajRmRNCncny95uD+2wmBHYOzc=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9c40bef08a5bdc0ccc3207f4282a1ded83e77a7a", + "rev": "f104cca31ba6c0403b678ad9428726476b503782", "type": "github" }, "original": { @@ -317,11 +317,11 @@ ] }, "locked": { - "lastModified": 1734575524, - "narHash": "sha256-BxQ/4JuHEi0zRjF0P8B5xnbXOLulgsK2gfwVRXGZ4a4=", + "lastModified": 1741314698, + "narHash": "sha256-6Yp0CTwAY/jq/F81Sa8NM0Zi1EwxAdASO6y4A5neGuc=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "573c674a3ad06e8a525263185ebef336a411d1d5", + "rev": "4e9af61c1a631886cdc7e13032af4fc9e75bb76b", "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": 1740437053, + "narHash": "sha256-exPTta4qI1ka9sk+jPcLogGffJ1OVXnAsTRqpeAXeNw=", "owner": "simple-nixos-mailserver", "repo": "nixos-mailserver", - "rev": "c43d8c4a3ce84a7bebd110b06e69365484db6208", + "rev": "c8ec4d5e432f5df4838eacd39c11828d23ce66ec", "type": "gitlab" }, "original": { @@ -378,11 +378,11 @@ ] }, "locked": { - "lastModified": 1734704479, - "narHash": "sha256-MMi74+WckoyEWBRcg/oaGRvXC9BVVxDZNRMpL+72wBI=", + "lastModified": 1739829690, + "narHash": "sha256-mL1szCeIsjh6Khn3nH2cYtwO5YXG6gBiTw1A30iGeDU=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "65712f5af67234dad91a5a4baee986a8b62dbf8f", + "rev": "3d0579f5cc93436052d94b73925b48973a104204", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 6f780d0..4d127f1 100644 --- a/flake.nix +++ b/flake.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/server2/configuration.nix b/hosts/by-name/server2/configuration.nix index 95f0ade..c373d28 100644 --- a/hosts/by-name/server2/configuration.nix +++ b/hosts/by-name/server2/configuration.nix @@ -14,11 +14,10 @@ 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 = { @@ -53,6 +52,7 @@ 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; 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/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/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..6905005 --- /dev/null +++ b/modules/by-name/st/stalwart-mail/module.nix @@ -0,0 +1,392 @@ +{ + 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 = {}; + users.stalwart-mail = { + isSystemUser = true; + group = "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..de75e29 --- /dev/null +++ b/modules/by-name/sy/system-info/module.nix @@ -0,0 +1,68 @@ +{ + 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"; + + "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/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 d90b504..8d3ae92 100644 --- a/secrets.nix +++ b/secrets.nix @@ -5,6 +5,9 @@ let server2HostKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL1TUFoCTplkqTVbXQ6qDCyeo2h8+C0vjrIlKu6vmq5f"; server3HostKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP3s4FjGx7LEVf/GE3WeCl8TmCtPt8gW1J0mp0fUJBNm"; + # WARNING(@bpeetz): ONLY use this key on age files that are meant to be public! <2025-02-23> + testingKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILxdvBk/PC9fC7B5vqe9TvygZKY6LgDQ2mXRdVrthBM/"; + publicKeys = { "server2" = [ soispha @@ -62,3 +65,9 @@ let ); 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/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..07a6e8c --- /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.debug.traceValSeqN 2 [ + (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/tests/by-name/em/email-dns/nodes/acme/certs/generate.client b/tests/by-name/em/email-dns/nodes/acme/certs/generate.client new file mode 100755 index 0000000..5930298 --- /dev/null +++ b/tests/by-name/em/email-dns/nodes/acme/certs/generate.client @@ -0,0 +1,44 @@ +#! /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 + +NAME=client +if [ $# -gt 0 ]; then + NAME="$1" +fi + +# Create a client key. +$CERTTOOL \ + --generate-privkey \ + --sec-param "$SEC_PARAM" \ + --key-type "$KEY_TYPE" \ + --outfile "$NAME".key.pem + +chmod 600 "$NAME".key.pem + +# Sign a client cert with the key. +cat <<EOF >"$NAME".template +dns_name = "$NAME" +dns_name = "$SAN" +expiration_days = $EXPIRATION_DAYS +organization = $ORGANIZATION +encryption_key +signing_key +EOF + +$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 + +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..97b9be7 --- /dev/null +++ b/tests/by-name/em/email-dns/secrets/dkim/alice.com/private.age @@ -0,0 +1,11 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFJzZ1dwUSAxanpG +VWxCYjF3aWFUUEM0b2Q2bGJUNEJZMGFlVkJpTFg3eUc1UGdyblF3CnlJVXY2Ti9z +SmltMmIzM25jajl3WE5kMkVsY05NUkpDdzlJVHdJYXByZjQKLT4gU0t5SSxMLWdy +ZWFzZSB6Tgova0Juc0x3RlFrR1NSVzBIMllYRmZiRXlzN2hSdHZQaFVDS3FPUFNr +NzBiM0Q5dExOREgydFpKWm1MaGQ5QkxBCgotLS0gNC81ZHQ5eFBrUGJxWXF6dWF4 +Mlg0WHBXS2RqeW1uY1hGUVJXbHpUaDhlWQpih0QTGjejnwIQ2lvDFS1wbNiiOJ+c +awJ2tX8chzWm+wOECaIZAqwW2NwVZj5Sj+Vzv6LQ1BVaQAiEN41GRvjyP/u3X+d+ +LKI3bPa8DWxQNd7/zAhFjSB1KEIBrqGb2GtW/Yv8Mu07V8IV/MaGUwpDOXgvFQVH +UQ1qpM0R1r190IuV2Y7M558J42crH9/5mIvMH5rW++Ru +-----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..6bd9e28 --- /dev/null +++ b/tests/by-name/em/email-dns/secrets/dkim/bob.com/private.age @@ -0,0 +1,13 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFJzZ1dwUSBtQ1lC +bmtXZWlqeE1SWmR2d1JTcEZMZFRKZW1jVnRGSHpUMEM1cnpvTkdFCkRVbmlNV0ZS +SDdobTFFQUcyU3dMVXlTditvZXN0L0pPN1ZHaWtxOFZSL1EKLT4gfSM6JGpjOlot +Z3JlYXNlICdRYiBLKlV6CmZmczdSa2U5cWl6OG5QL0VaUGsyNUlCVFJ1UjJxVnpV +ZE1sN2lSRTgzVjI1S3pJVzdqN05WUVZmaTRYMXptb0kKczJEOG9EM2xtMFRHd3Vt +TUpiK2RzZkRwZTZqb3lEOGpKNy80Vk9BVDlSNjhYSkROYlVGQ1ZESGhIV3ZJWVEK +LS0tIEk0MXVEci9ITERYRzZFbTJJQWxSQzhFV3NqV2o3M0NvVlNhLzhhVkJYcTQK +GJtIH4AxSSwZhnLn5IUhOihz9Ai2lLnf00uhvF6+i29TtyEgxgWhisBJtzShB/Aq +Bct5em093jryJPNQBNDJpImEViP9WS/kTqQG0bnu2i/Nr5+vZyRcK8qv75guMxki +p7sUirbzCNtA+5JGGJb30PqOAWpflBPL0fkC5L7JyAjhNRCOgIL+QQS3mosU1AYJ +izFOdod2DA== +-----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..1e090f4 --- /dev/null +++ b/tests/by-name/em/email-dns/secrets/dkim/gen_key.sh @@ -0,0 +1,33 @@ +#! /usr/bin/env nix-shell +#! nix-shell -p rage -p openssl -p dash -i dash --impure + +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..03bb0b1 --- /dev/null +++ b/tests/by-name/em/email-dns/secrets/dkim/mail1.server.com/private.age @@ -0,0 +1,10 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFJzZ1dwUSBiZmcz +OVlacDhjS3pCWlZCYlpyVVFoQWxFN1Z0eTJIY2ovNVg2MjQ1SXpVClRZUnhkblFI +c0VyelMxYzBsZ2NMUEVzWmtTSzJuVTdoVHFiZEc5cEd3aHMKLT4gJyctZ3JlYXNl +IG1tIFt2YSAwCkQvY2VnMlBTSHhPbXJ2NE55ck0KLS0tIGkxWHdQb0NIVmZyaTdW +bmorU2NLMjByakpTMlo1NUtFQ0NFd1YvOC9EaFEKtDNLHVtnsFiyhsREJOPq1xlk +74MURNlYnlF1IMrUaA3oUQSR5M34Crg7rHtjF54OsRhm79Y1dGHWeeC3evVNVpY3 +1dn/q/12aWIzT/TgGcSi3bK5fPkv+nMs/WPKTREHJ1HcWLGDeH6e8uTV7lAwiSuP +PjYhDbnNUCMMyaBsgbtCnMe8HuHdTwXQWuh0dApS5iL3z8qoSQ== +-----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..6768973 --- /dev/null +++ b/tests/by-name/em/email-dns/secrets/dkim/mail2.server.com/private.age @@ -0,0 +1,13 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFJzZ1dwUSBBZ3Ex +ZWNUU01JK0ZGOEhEWUpEM3JLdXpjc3doMVA2RUI5TVVpblQxS0JJCnlHRmdVVk05 +MW56cEt4M00raE1nU3JZaTMvTXdUQXdzbTYvVElqVjdvNWcKLT4gRnJFLWdyZWFz +ZSA5bEhvWn4Kb01McHBBekVTalcwM0tob3VUd3NuVFlvZUpnSFQxbXVBaEJNMVlQ +K1BiSjRCL1YrZDZoSnFBNU9aQkQyNjRoSwpqcnBnd2NJQlMxaHdoa0pPWGR0SEZO +SU5DNjFxb3JQTTZITVZNRGF1VUR4Zm9laWhYd3lHZityRTNJVVF1bXdwCnhGYzMK +LS0tIFFsN0Q3V1pxWUduSU9xd21uVEF2R0tJcURYa1FOTS9kMDh6RGkwNS9SMUEK +Ni+1WbmAiavBCwLg8r1nvVipXQJ2/cItN1MgWlYe0+UrgLxRU5VLhoWi9BEulGEV +KHkNWyMCK4Tl/NJt1PAQVJ6QBVHYYxIYQWY1QkNCqXe1YdaJ5jDcWGSZdhbCrzMN +3tx3EPhigU2DiQZB6l4OOaHLjAw2a+POVwwsCavnRp7vEhs/5O2t5Lo2vCoDGCot +6o+Sdr86mw== +-----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") + ''; + } |