diff --git a/modules/misc/news.nix b/modules/misc/news.nix index dc605ae4c..395a45d5f 100644 --- a/modules/misc/news.nix +++ b/modules/misc/news.nix @@ -1653,6 +1653,23 @@ in { ''; } + { + time = "2024-05-21T20:22:57+00:00"; + condition = config.programs.git.signing != { }; + message = '' + The Git module now supports signing via SSH and X.509 keys, in addition to OpenPGP/GnuPG, + via the `programs.git.signing.format` option. + + The format defaults to `openpgp` for now, due to backwards compatibility reasons — this is + not guaranteed to last! GPG users should manually set `programs.git.signing.format` to + `openpgp` as soon as possible. + + Accordingly, `programs.git.signing.gpgPath` has been renamed to the more generic option + `programs.git.signing.signer` as not everyone uses GPG. + Please migrate to the new option to suppress the generated warning. + ''; + } + { time = "2024-05-25T14:36:03+00:00"; message = '' diff --git a/modules/programs/git.nix b/modules/programs/git.nix index a8b1c5942..b3cb57c28 100644 --- a/modules/programs/git.nix +++ b/modules/programs/git.nix @@ -14,34 +14,6 @@ let supersectionType = attrsOf (either multipleType sectionType); in attrsOf supersectionType; - signModule = types.submodule { - options = { - key = mkOption { - type = types.nullOr types.str; - default = null; - description = '' - The default GPG signing key fingerprint. - - Set to `null` to let GnuPG decide what signing key - to use depending on commit’s author. - ''; - }; - - signByDefault = mkOption { - type = types.bool; - default = false; - description = "Whether commits and tags should be signed by default."; - }; - - gpgPath = mkOption { - type = types.str; - default = "${pkgs.gnupg}/bin/gpg2"; - defaultText = "\${pkgs.gnupg}/bin/gpg2"; - description = "Path to GnuPG binary to use."; - }; - }; - }; - includeModule = types.submodule ({ config, ... }: { options = { condition = mkOption { @@ -133,10 +105,40 @@ in { description = "Git aliases to define."; }; - signing = mkOption { - type = types.nullOr signModule; - default = null; - description = "Options related to signing commits using GnuPG."; + signing = { + key = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The default signing key fingerprint. + + Set to `null` to let the signer decide what signing key + to use depending on commit’s author. + ''; + }; + + format = mkOption { + type = types.enum [ "openpgp" "ssh" "x509" ]; + defaultText = literalExpression '' + "openpgp" for state version < 24.11, + undefined for state version ≥ 24.11 + ''; + description = '' + The signing method to use when signing commits and tags. + Valid values are `openpgp` (OpenPGP/GnuPG), `ssh` (SSH), and `x509` (X.509 certificates). + ''; + }; + + signByDefault = mkOption { + type = types.bool; + default = false; + description = "Whether commits and tags should be signed by default."; + }; + + signer = mkOption { + type = types.str; + description = "Path to signer binary to use."; + }; }; extraConfig = mkOption { @@ -409,9 +411,19 @@ in { }; }; + imports = [ + (mkRenamedOptionModule [ "programs" "git" "signing" "gpgPath" ] [ + "programs" + "git" + "signing" + "signer" + ]) + ]; + config = mkIf cfg.enable (mkMerge [ { home.packages = [ cfg.package ]; + assertions = [{ assertion = let enabled = [ @@ -475,12 +487,28 @@ in { (filterAttrs hasSmtp config.accounts.email.accounts); } - (mkIf (cfg.signing != null) { - programs.git.iniContent = { - user.signingKey = mkIf (cfg.signing.key != null) cfg.signing.key; - commit.gpgSign = mkDefault cfg.signing.signByDefault; - tag.gpgSign = mkDefault cfg.signing.signByDefault; - gpg.program = cfg.signing.gpgPath; + (mkIf (cfg.signing != { }) { + programs.git = { + signing = { + format = mkIf (versionOlder config.home.stateVersion "24.11") + (mkOptionDefault "openpgp"); + signer = mkIf (cfg.signing.format != null) (mkOptionDefault { + openpgp = getExe config.programs.gpg.package; + ssh = getExe' pkgs.openssh "ssh-keygen"; + x509 = getExe' config.programs.gpg.package "gpgsm"; + }.${cfg.signing.format}); + }; + + iniContent = let inherit (cfg.signing) format; + in { + user.signingKey = mkIf (cfg.signing.key != null) cfg.signing.key; + commit.gpgSign = mkDefault cfg.signing.signByDefault; + tag.gpgSign = mkDefault cfg.signing.signByDefault; + gpg = { + inherit format; + ${format}.program = cfg.signing.signer; + }; + }; }; }) diff --git a/tests/modules/programs/gh/credential-helper.git.conf b/tests/modules/programs/gh/credential-helper.git.conf index 529d6725a..8b712cd86 100644 --- a/tests/modules/programs/gh/credential-helper.git.conf +++ b/tests/modules/programs/gh/credential-helper.git.conf @@ -1,5 +1,17 @@ +[commit] + gpgSign = false + [credential "https://github.com"] helper = "@gh@/bin/gh auth git-credential" [credential "https://github.example.com"] helper = "@gh@/bin/gh auth git-credential" + +[gpg] + format = "openpgp" + +[gpg "openpgp"] + program = "path-to-gpg" + +[tag] + gpgSign = false diff --git a/tests/modules/programs/gh/credential-helper.nix b/tests/modules/programs/gh/credential-helper.nix index 547ad446a..b0cad9454 100644 --- a/tests/modules/programs/gh/credential-helper.nix +++ b/tests/modules/programs/gh/credential-helper.nix @@ -7,7 +7,10 @@ }; }; - programs.git.enable = true; + programs.git = { + enable = true; + signing.signer = "path-to-gpg"; + }; nmt.script = '' assertFileExists home-files/.config/git/config diff --git a/tests/modules/programs/git/default.nix b/tests/modules/programs/git/default.nix index 3b92b2ff1..a1211f254 100644 --- a/tests/modules/programs/git/default.nix +++ b/tests/modules/programs/git/default.nix @@ -3,6 +3,7 @@ git-with-most-options = ./git.nix; git-with-msmtp = ./git-with-msmtp.nix; git-with-str-extra-config = ./git-with-str-extra-config.nix; + git-with-signing-key-id-legacy = ./git-with-signing-key-id-legacy.nix; git-with-signing-key-id = ./git-with-signing-key-id.nix; git-without-signing-key-id = ./git-without-signing-key-id.nix; git-with-hooks = ./git-with-hooks.nix; diff --git a/tests/modules/programs/git/git-expected.conf b/tests/modules/programs/git/git-expected.conf index 61e0a2cc5..3d072b404 100644 --- a/tests/modules/programs/git/git-expected.conf +++ b/tests/modules/programs/git/git-expected.conf @@ -38,6 +38,9 @@ smudge = "git-lfs smudge -- %f" [gpg] + format = "openpgp" + +[gpg "openpgp"] program = "path-to-gpg" [interactive] diff --git a/tests/modules/programs/git/git-with-email-expected.conf b/tests/modules/programs/git/git-with-email-expected.conf index fa027422f..d7c40973a 100644 --- a/tests/modules/programs/git/git-with-email-expected.conf +++ b/tests/modules/programs/git/git-with-email-expected.conf @@ -1,3 +1,12 @@ +[commit] + gpgSign = false + +[gpg] + format = "openpgp" + +[gpg "openpgp"] + program = "path-to-gpg" + [sendemail "hm-account"] from = "H. M. Test Jr. " smtpEncryption = "tls" @@ -12,6 +21,9 @@ smtpSslCertPath = "/etc/ssl/certs/ca-certificates.crt" smtpUser = "home.manager" +[tag] + gpgSign = false + [user] email = "hm@example.com" name = "H. M. Test" diff --git a/tests/modules/programs/git/git-with-email.nix b/tests/modules/programs/git/git-with-email.nix index db6053a53..c1a375b9b 100644 --- a/tests/modules/programs/git/git-with-email.nix +++ b/tests/modules/programs/git/git-with-email.nix @@ -8,6 +8,7 @@ programs.git = { enable = true; + signing.signer = "path-to-gpg"; userEmail = "hm@example.com"; userName = "H. M. Test"; }; diff --git a/tests/modules/programs/git/git-with-msmtp-expected.conf b/tests/modules/programs/git/git-with-msmtp-expected.conf index 8cd5d86f0..8cf588c7a 100644 --- a/tests/modules/programs/git/git-with-msmtp-expected.conf +++ b/tests/modules/programs/git/git-with-msmtp-expected.conf @@ -1,3 +1,12 @@ +[commit] + gpgSign = false + +[gpg] + format = "openpgp" + +[gpg "openpgp"] + program = "path-to-gpg" + [sendemail "hm-account"] from = "H. M. Test Jr. " smtpEncryption = "tls" @@ -10,6 +19,9 @@ from = "H. M. Test " smtpServer = "@msmtp@/bin/msmtp" +[tag] + gpgSign = false + [user] email = "hm@example.com" name = "H. M. Test" diff --git a/tests/modules/programs/git/git-with-msmtp.nix b/tests/modules/programs/git/git-with-msmtp.nix index 917955130..5b5416189 100644 --- a/tests/modules/programs/git/git-with-msmtp.nix +++ b/tests/modules/programs/git/git-with-msmtp.nix @@ -7,6 +7,7 @@ programs.git = { enable = true; + signing.signer = "path-to-gpg"; userEmail = "hm@example.com"; userName = "H. M. Test"; }; diff --git a/tests/modules/programs/git/git-with-signing-key-id-expected.conf b/tests/modules/programs/git/git-with-signing-key-id-expected.conf index b26377aac..aefca4142 100644 --- a/tests/modules/programs/git/git-with-signing-key-id-expected.conf +++ b/tests/modules/programs/git/git-with-signing-key-id-expected.conf @@ -2,7 +2,10 @@ gpgSign = true [gpg] - program = "path-to-gpg" + format = "ssh" + +[gpg "ssh"] + program = "path-to-ssh" [tag] gpgSign = true @@ -10,4 +13,4 @@ [user] email = "user@example.org" name = "John Doe" - signingKey = "00112233445566778899AABBCCDDEEFF" + signingKey = "ssh-ed25519 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" diff --git a/tests/modules/programs/git/git-with-signing-key-id-legacy-expected.conf b/tests/modules/programs/git/git-with-signing-key-id-legacy-expected.conf new file mode 100644 index 000000000..7c9bc1d02 --- /dev/null +++ b/tests/modules/programs/git/git-with-signing-key-id-legacy-expected.conf @@ -0,0 +1,16 @@ +[commit] + gpgSign = true + +[gpg] + format = "openpgp" + +[gpg "openpgp"] + program = "path-to-gpg" + +[tag] + gpgSign = true + +[user] + email = "user@example.org" + name = "John Doe" + signingKey = "00112233445566778899AABBCCDDEEFF" diff --git a/tests/modules/programs/git/git-with-signing-key-id-legacy.nix b/tests/modules/programs/git/git-with-signing-key-id-legacy.nix new file mode 100644 index 000000000..36f092299 --- /dev/null +++ b/tests/modules/programs/git/git-with-signing-key-id-legacy.nix @@ -0,0 +1,28 @@ +{ lib, options, ... }: { + config = { + programs.git = { + enable = true; + userName = "John Doe"; + userEmail = "user@example.org"; + + signing = { + gpgPath = "path-to-gpg"; + key = "00112233445566778899AABBCCDDEEFF"; + signByDefault = true; + }; + }; + + test.asserts.warnings.expected = [ + "The option `programs.git.signing.gpgPath' defined in ${ + lib.showFiles options.programs.git.signing.gpgPath.files + } has been renamed to `programs.git.signing.signer'." + ]; + + nmt.script = '' + assertFileExists home-files/.config/git/config + assertFileContent home-files/.config/git/config ${ + ./git-with-signing-key-id-legacy-expected.conf + } + ''; + }; +} diff --git a/tests/modules/programs/git/git-with-signing-key-id.nix b/tests/modules/programs/git/git-with-signing-key-id.nix index a6386e347..a0aae8b53 100644 --- a/tests/modules/programs/git/git-with-signing-key-id.nix +++ b/tests/modules/programs/git/git-with-signing-key-id.nix @@ -5,8 +5,10 @@ userEmail = "user@example.org"; signing = { - gpgPath = "path-to-gpg"; - key = "00112233445566778899AABBCCDDEEFF"; + signer = "path-to-ssh"; + format = "ssh"; + key = + "ssh-ed25519 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; signByDefault = true; }; }; diff --git a/tests/modules/programs/git/git-with-str-extra-config-expected.conf b/tests/modules/programs/git/git-with-str-extra-config-expected.conf index 071268e83..2154bc580 100644 --- a/tests/modules/programs/git/git-with-str-extra-config-expected.conf +++ b/tests/modules/programs/git/git-with-str-extra-config-expected.conf @@ -1,5 +1,17 @@ This can be anything. +[commit] + gpgSign = false + +[gpg] + format = "openpgp" + +[gpg "openpgp"] + program = "path-to-gpg" + +[tag] + gpgSign = false + [user] email = "user@example.org" name = "John Doe" diff --git a/tests/modules/programs/git/git-with-str-extra-config.nix b/tests/modules/programs/git/git-with-str-extra-config.nix index d9bd7e572..90ed363b2 100644 --- a/tests/modules/programs/git/git-with-str-extra-config.nix +++ b/tests/modules/programs/git/git-with-str-extra-config.nix @@ -1,6 +1,7 @@ { programs.git = { enable = true; + signing.signer = "path-to-gpg"; extraConfig = '' This can be anything. ''; diff --git a/tests/modules/programs/git/git-without-signing-key-id-expected.conf b/tests/modules/programs/git/git-without-signing-key-id-expected.conf index 8c04aeda1..29452a2db 100644 --- a/tests/modules/programs/git/git-without-signing-key-id-expected.conf +++ b/tests/modules/programs/git/git-without-signing-key-id-expected.conf @@ -2,6 +2,9 @@ gpgSign = true [gpg] + format = "openpgp" + +[gpg "openpgp"] program = "path-to-gpg" [tag] diff --git a/tests/modules/programs/git/git-without-signing-key-id.nix b/tests/modules/programs/git/git-without-signing-key-id.nix index a06cb692e..adc7995bf 100644 --- a/tests/modules/programs/git/git-without-signing-key-id.nix +++ b/tests/modules/programs/git/git-without-signing-key-id.nix @@ -5,7 +5,7 @@ userEmail = "user@example.org"; signing = { - gpgPath = "path-to-gpg"; + signer = "path-to-gpg"; key = null; signByDefault = true; }; diff --git a/tests/modules/programs/git/git.nix b/tests/modules/programs/git/git.nix index 51ed97180..2e436bc75 100644 --- a/tests/modules/programs/git/git.nix +++ b/tests/modules/programs/git/git.nix @@ -52,7 +52,8 @@ in { } ]; signing = { - gpgPath = "path-to-gpg"; + signer = "path-to-gpg"; + format = "openpgp"; key = "00112233445566778899AABBCCDDEEFF"; signByDefault = true; };