diff --git a/ReleaseBuilder/Build/Command.GpgSign.cs b/ReleaseBuilder/Build/Command.GpgSign.cs
new file mode 100644
index 000000000..6f9b2f13e
--- /dev/null
+++ b/ReleaseBuilder/Build/Command.GpgSign.cs
@@ -0,0 +1,52 @@
+namespace ReleaseBuilder.Build;
+
+public static partial class Command
+{
+ ///
+ /// Implementation of the gpg sign command
+ ///
+ private static class GpgSign
+ {
+ ///
+ /// Performs a GPG sign operation on the files
+ ///
+ /// The files to sign
+ /// The runtime configuration
+ /// An awaitable task
+ public static async Task SignReleaseFiles(IEnumerable files, RuntimeConfig rtcfg)
+ {
+ var (gpgid, passphrase) = GetGpgIdAndPassphrase(rtcfg);
+
+ foreach (var file in files)
+ foreach (var armored in new[] { true, false })
+ await ProcessHelper.Execute(
+ [
+ Program.Configuration.Commands.Gpg!,
+ "--pinentry-mode", "loopback",
+ "--passphrase-fd", "0",
+ "--batch", "--yes",
+ armored ? "--armor" : string.Empty,
+ "-u", gpgid,
+ "--output", file + (armored ? "sig.asc" : ".sig"),
+ "--detach-sign", file
+ ],
+ workingDirectory: Path.GetDirectoryName(file),
+ writeStdIn: (stdin) => stdin.WriteLineAsync(passphrase)
+ );
+ }
+
+ ///
+ /// Gets the GPG ID and passphrase from the keyfile
+ ///
+ /// The runtime configuration
+ /// The GPG ID and passphrase
+ static (string GpgId, string GpgPassphrase) GetGpgIdAndPassphrase(RuntimeConfig rtcfg)
+ {
+ using var ms = new MemoryStream();
+ using var fs = File.OpenRead(Program.Configuration.ConfigFiles.GpgKeyfile);
+ SharpAESCrypt.SharpAESCrypt.Decrypt(rtcfg.KeyfilePassword, fs, ms);
+ var parts = System.Text.Encoding.UTF8.GetString(ms.ToArray()).Split('\n', 2, StringSplitOptions.RemoveEmptyEntries);
+ return (parts[0], parts[1]);
+ }
+ }
+}
diff --git a/ReleaseBuilder/Build/Command.RuntimeConfig.cs b/ReleaseBuilder/Build/Command.RuntimeConfig.cs
new file mode 100644
index 000000000..ab6d8b72c
--- /dev/null
+++ b/ReleaseBuilder/Build/Command.RuntimeConfig.cs
@@ -0,0 +1,320 @@
+using System.Security.Cryptography;
+
+namespace ReleaseBuilder.Build;
+
+public static partial class Command
+{
+ ///
+ /// Setup of the current runtime information
+ ///
+ private class RuntimeConfig
+ {
+ ///
+ /// Constructs a new
+ ///
+ /// The release info to use
+ /// The keyfile password to use
+ /// The sign keys
+ /// The changelog news
+ /// The command input
+ public RuntimeConfig(ReleaseInfo releaseInfo, IEnumerable signKeys, string keyfilePassword, string changelogNews, CommandInput input)
+ {
+ ReleaseInfo = releaseInfo;
+ SignKeys = signKeys;
+ KeyfilePassword = keyfilePassword;
+ ChangelogNews = changelogNews;
+ Input = input;
+ }
+
+ ///
+ /// The cached password for the pfx file
+ ///
+ private string? _pfxPassword = null;
+
+ ///
+ /// The commandline input
+ ///
+ private CommandInput Input { get; }
+
+ ///
+ /// The release info for this run
+ ///
+ public ReleaseInfo ReleaseInfo { get; }
+
+ ///
+ /// The keyfile password for this run
+ ///
+ public IEnumerable SignKeys { get; }
+
+ ///
+ /// The primary password
+ ///
+ public string KeyfilePassword { get; }
+
+ ///
+ /// The changelog news
+ ///
+ public string ChangelogNews { get; }
+
+ ///
+ /// Gets the PFX password and throws if not possible
+ ///
+ public string PfxPassword
+ => string.IsNullOrWhiteSpace(_pfxPassword)
+ ? _pfxPassword = GetAuthenticodePassword(KeyfilePassword)
+ : _pfxPassword;
+
+ ///
+ /// Cache value for checking if authenticode signing is enabled
+ ///
+ private bool? _useAuthenticodeSigning;
+
+ ///
+ /// Checks if Authenticode signing should be enabled
+ ///
+ public void ToggleAuthenticodeSigning()
+ {
+ if (!_useAuthenticodeSigning.HasValue)
+ {
+ if (Input.DisableAuthenticode)
+ {
+ _useAuthenticodeSigning = false;
+ return;
+ }
+
+ if (Program.Configuration.IsAuthenticodePossible())
+ _useAuthenticodeSigning = true;
+ else
+ {
+ if (ConsoleHelper.ReadInput("Configuration missing for osslsigncode, continue without signing executables?", "Y", "n") == "Y")
+ {
+ _useAuthenticodeSigning = false;
+ return;
+ }
+
+ throw new Exception("Configuration is not set up for osslsigncode");
+ }
+ }
+ }
+
+ ///
+ /// Cache value for checking if codesign is possible
+ ///
+ private bool? _useCodeSignSigning;
+
+ ///
+ /// Checks if codesign is enabled
+ ///
+ public void ToggleSignCodeSigning()
+ {
+ if (!_useCodeSignSigning.HasValue)
+ {
+ if (Input.DisableSignCode)
+ {
+ _useCodeSignSigning = false;
+ return;
+ }
+
+ if (!OperatingSystem.IsMacOS())
+ _useCodeSignSigning = false;
+ else if (Program.Configuration.IsCodeSignPossible())
+ _useCodeSignSigning = true;
+ else
+ {
+ if (ConsoleHelper.ReadInput("Configuration missing for signcode, continue without signing executables?", "Y", "n") == "Y")
+ {
+ _useCodeSignSigning = false;
+ return;
+ }
+
+ throw new Exception("Configuration is not set up for signcode");
+ }
+ }
+ }
+
+ ///
+ /// Cache value for checking if docker build is enabled
+ ///
+ private bool? _dockerBuild;
+
+ ///
+ /// Checks if docker build is enabled
+ ///
+ public async Task ToggleDockerBuild()
+ {
+ if (!_dockerBuild.HasValue)
+ {
+ try
+ {
+ var res = await ProcessHelper.ExecuteWithOutput([Program.Configuration.Commands.Docker!, "ps"], suppressStdErr: true);
+ _dockerBuild = true;
+ }
+ catch
+ {
+
+ if (ConsoleHelper.ReadInput("Docker does not seem to be running, continue without docker builds?", "Y", "n") == "Y")
+ {
+ _dockerBuild = false;
+ return;
+ }
+
+ throw new Exception("Docker is not running, and is required for building Docker images");
+ }
+ }
+ }
+
+ ///
+ /// Cache value for checking if notarize is enabled
+ ///
+ private bool? _useNotarizeSigning;
+
+ ///
+ /// Checks if notarize signing is enabled
+ ///
+ public void ToggleNotarizeSigning()
+ {
+ if (!_useNotarizeSigning.HasValue)
+ {
+ if (Input.DisableNotarizeSigning)
+ {
+ _useNotarizeSigning = false;
+ return;
+ }
+
+ if (!OperatingSystem.IsMacOS())
+ _useNotarizeSigning = false;
+ else if (Program.Configuration.IsNotarizePossible())
+ _useNotarizeSigning = true;
+ else
+ {
+ if (ConsoleHelper.ReadInput("Configuration missing for notarize, continue without notarizing executables?", "Y", "n") == "Y")
+ {
+ _useNotarizeSigning = false;
+ return;
+ }
+
+ throw new Exception("Configuration is not set up for notarize");
+ }
+ }
+ }
+
+ ///
+ /// Cache value for checking if GPG signing is enabled
+ ///
+ private bool? _useGpgSigning;
+
+ public void ToggleGpgSigning()
+ {
+ if (!_useGpgSigning.HasValue)
+ {
+ if (Input.DisableGpgSigning)
+ {
+ _useGpgSigning = false;
+ return;
+ }
+
+ if (ConsoleHelper.ReadInput("Configuration missing for gpg, continue without gpg signing packages?", "Y", "n") == "Y")
+ {
+ _useGpgSigning = false;
+ return;
+ }
+
+ throw new Exception("Configuration is not set up for gpg");
+ }
+ }
+
+ ///
+ /// Returns a value indicating if codesign is enabled
+ ///
+ public bool UseCodeSignSigning => _useCodeSignSigning!.Value;
+
+ ///
+ /// Returns a value indicating if authenticode signing is enabled
+ ///
+ public bool UseAuthenticodeSigning => _useAuthenticodeSigning!.Value;
+
+ ///
+ /// Returns a value indicating if notarize is enabled
+ ///
+ public bool UseNotarizeSigning => _useNotarizeSigning!.Value;
+
+ ///
+ /// Returns a value indicating if GPG signing is enabled
+ ///
+ public bool UseGPGSigning => _useGpgSigning!.Value;
+
+ ///
+ /// Returns a value indicating if docker build is enabled
+ ///
+ public bool UseDockerBuild => _dockerBuild!.Value;
+
+ ///
+ /// Gets the MacOS app bundle name
+ ///
+ public string MacOSAppName => Input.MacOSAppName;
+
+ ///
+ /// The docker repository to use
+ ///
+ public string DockerRepo => Input.DockerRepo;
+
+ ///
+ /// Gets a value indicating if pushing should be enabled
+ ///
+ public bool PushToDocker => !Input.DisableDockerPush;
+
+ ///
+ /// Decrypts the password file and returns the PFX password
+ ///
+ /// Password for the password file
+ /// The Authenticode password
+ private string GetAuthenticodePassword(string keyfilepassword)
+ => EncryptionHelper.DecryptPasswordFile(Program.Configuration.ConfigFiles.AuthenticodePasswordFile, keyfilepassword);
+
+ ///
+ /// Performs authenticode signing if enabled
+ ///
+ /// The file to sign
+ /// An awaitable task
+ public Task AuthenticodeSign(string file)
+ => UseAuthenticodeSigning
+ ? ProcessRunner.OsslCodeSign(
+ Program.Configuration.Commands.OsslSignCode!,
+ Program.Configuration.ConfigFiles.AuthenticodePfxFile,
+ PfxPassword,
+ file)
+ : Task.CompletedTask;
+
+ ///
+ /// Performs codesign on the given file
+ ///
+ /// The file to sign
+ /// The entitlements to apply
+ /// An awaitable task
+ public Task Codesign(string file, string entitlements)
+ => UseCodeSignSigning
+ ? ProcessRunner.MacOSCodeSign(
+ Program.Configuration.Commands.Codesign!,
+ Program.Configuration.ConfigFiles.CodesignIdentity,
+ entitlements,
+ file
+ )
+ : Task.CompletedTask;
+
+ ///
+ /// Performs productsign on the given file
+ ///
+ /// The file to sign
+ /// An awaitable task
+ public Task Productsign(string file)
+ => UseCodeSignSigning
+ ? ProcessRunner.MacOSProductSign(
+ Program.Configuration.Commands.Productsign!,
+ Program.Configuration.ConfigFiles.CodesignIdentity,
+ file
+ )
+ : Task.CompletedTask;
+
+ }
+
+}
diff --git a/ReleaseBuilder/Build/Command.cs b/ReleaseBuilder/Build/Command.cs
index 53350519a..b98b1222a 100644
--- a/ReleaseBuilder/Build/Command.cs
+++ b/ReleaseBuilder/Build/Command.cs
@@ -63,290 +63,6 @@ public static partial class Command
///
private static readonly IReadOnlyList FedoraCLIDepends = [];
- ///
- /// Setup of the current runtime information
- ///
- /// The release info for the current build
- /// The keyfile password
- private class RuntimeConfig
- {
- ///
- /// Constructs a new
- ///
- /// The release info to use
- /// The keyfile password to use
- /// The sign keys
- /// The changelog news
- /// The command input
- public RuntimeConfig(ReleaseInfo releaseInfo, IEnumerable signKeys, string keyfilePassword, string changelogNews, CommandInput input)
- {
- ReleaseInfo = releaseInfo;
- SignKeys = signKeys;
- KeyfilePassword = keyfilePassword;
- ChangelogNews = changelogNews;
- Input = input;
- }
-
- ///
- /// The cached password for the pfx file
- ///
- private string? _pfxPassword = null;
-
- ///
- /// The commandline input
- ///
- private CommandInput Input { get; }
-
- ///
- /// The release info for this run
- ///
- public ReleaseInfo ReleaseInfo { get; }
-
- ///
- /// The keyfile password for this run
- ///
- public IEnumerable SignKeys { get; }
-
- ///
- /// The primary password
- ///
- public string KeyfilePassword { get; }
-
- ///
- /// The changelog news
- ///
- public string ChangelogNews { get; }
-
- ///
- /// Gets the PFX password and throws if not possible
- ///
- public string PfxPassword
- => string.IsNullOrWhiteSpace(_pfxPassword)
- ? _pfxPassword = GetAuthenticodePassword(KeyfilePassword)
- : _pfxPassword;
-
- ///
- /// Cache value for checking if authenticode signing is enabled
- ///
- private bool? _useAuthenticodeSigning;
-
- ///
- /// Checks if Authenticode signing should be enabled
- ///
- public void ToggleAuthenticodeSigning()
- {
- if (!_useAuthenticodeSigning.HasValue)
- {
- if (Input.DisableAuthenticode)
- {
- _useAuthenticodeSigning = false;
- return;
- }
-
- if (Program.Configuration.IsAuthenticodePossible())
- _useAuthenticodeSigning = true;
- else
- {
- if (ConsoleHelper.ReadInput("Configuration missing for osslsigncode, continue without signing executables?", "Y", "n") == "Y")
- {
- _useAuthenticodeSigning = false;
- return;
- }
-
- throw new Exception("Configuration is not set up for osslsigncode");
- }
- }
- }
-
- ///
- /// Cache value for checking if codesign is possible
- ///
- private bool? _useCodeSignSigning;
-
- ///
- /// Checks if codesign is enabled
- ///
- public void ToggleSignCodeSigning()
- {
- if (!_useCodeSignSigning.HasValue)
- {
- if (Input.DisableSignCode)
- {
- _useCodeSignSigning = false;
- return;
- }
-
- if (!OperatingSystem.IsMacOS())
- _useCodeSignSigning = false;
- else if (Program.Configuration.IsCodeSignPossible())
- _useCodeSignSigning = true;
- else
- {
- if (ConsoleHelper.ReadInput("Configuration missing for signcode, continue without signing executables?", "Y", "n") == "Y")
- {
- _useCodeSignSigning = false;
- return;
- }
-
- throw new Exception("Configuration is not set up for signcode");
- }
- }
- }
-
- ///
- /// Cache value for checking if docker build is enabled
- ///
- private bool? _dockerBuild;
-
- ///
- /// Checks if docker build is enabled
- ///
- public async Task ToggleDockerBuild()
- {
- if (!_dockerBuild.HasValue)
- {
- try
- {
- var res = await ProcessHelper.ExecuteWithOutput([Program.Configuration.Commands.Docker!, "ps"], suppressStdErr: true);
- _dockerBuild = true;
- }
- catch
- {
-
- if (ConsoleHelper.ReadInput("Docker does not seem to be running, continue without docker builds?", "Y", "n") == "Y")
- {
- _dockerBuild = false;
- return;
- }
-
- throw new Exception("Docker is not running, and is required for building Docker images");
- }
- }
- }
-
- ///
- /// Cache value for checking if notarize is enabled
- ///
- private bool? _useNotarizeSigning;
-
- ///
- /// Checks if notarize signing is enabled
- ///
- public void ToggleNotarizeSigning()
- {
- if (!_useNotarizeSigning.HasValue)
- {
- if (Input.DisableNotarizeSigning)
- {
- _useNotarizeSigning = false;
- return;
- }
-
- if (!OperatingSystem.IsMacOS())
- _useNotarizeSigning = false;
- else if (Program.Configuration.IsNotarizePossible())
- _useNotarizeSigning = true;
- else
- {
- if (ConsoleHelper.ReadInput("Configuration missing for notarize, continue without notarizing executables?", "Y", "n") == "Y")
- {
- _useNotarizeSigning = false;
- return;
- }
-
- throw new Exception("Configuration is not set up for notarize");
- }
- }
- }
-
- ///
- /// Returns a value indicating if codesign is enabled
- ///
- public bool UseCodeSignSigning => _useCodeSignSigning!.Value;
-
- ///
- /// Returns a value indicating if authenticode signing is enabled
- ///
- public bool UseAuthenticodeSigning => _useAuthenticodeSigning!.Value;
-
- ///
- /// Returns a value indicating if notarize is enabled
- ///
- public bool UseNotarizeSigning => _useNotarizeSigning!.Value;
-
- ///
- /// Returns a value indicating if docker build is enabled
- ///
- public bool UseDockerBuild => _dockerBuild!.Value;
-
- ///
- /// Gets the MacOS app bundle name
- ///
- public string MacOSAppName => Input.MacOSAppName;
-
- ///
- /// The docker repository to use
- ///
- public string DockerRepo => Input.DockerRepo;
-
- ///
- /// Gets a value indicating if pushing should be enabled
- ///
- public bool PushToDocker => !Input.DisableDockerPush;
-
- ///
- /// Decrypts the password file and returns the PFX password
- ///
- /// Password for the password file
- /// The Authenticode password
- private string GetAuthenticodePassword(string keyfilepassword)
- => EncryptionHelper.DecryptPasswordFile(Program.Configuration.ConfigFiles.AuthenticodePasswordFile, keyfilepassword);
-
- ///
- /// Performs authenticode signing if enabled
- ///
- /// The file to sign
- /// An awaitable task
- public Task AuthenticodeSign(string file)
- => UseAuthenticodeSigning
- ? ProcessRunner.OsslCodeSign(
- Program.Configuration.Commands.OsslSignCode!,
- Program.Configuration.ConfigFiles.AuthenticodePfxFile,
- PfxPassword,
- file)
- : Task.CompletedTask;
-
- ///
- /// Performs codesign on the given file
- ///
- /// The file to sign
- /// The entitlements to apply
- /// An awaitable task
- public Task Codesign(string file, string entitlements)
- => UseCodeSignSigning
- ? ProcessRunner.MacOSCodeSign(
- Program.Configuration.Commands.Codesign!,
- Program.Configuration.ConfigFiles.CodesignIdentity,
- entitlements,
- file
- )
- : Task.CompletedTask;
-
- ///
- /// Performs productsign on the given file
- ///
- /// The file to sign
- /// An awaitable task
- public Task Productsign(string file)
- => UseCodeSignSigning
- ? ProcessRunner.MacOSProductSign(
- Program.Configuration.Commands.Productsign!,
- Program.Configuration.ConfigFiles.CodesignIdentity,
- file
- )
- : Task.CompletedTask;
-
- }
///
/// Structure for keeping all variables for a single release
@@ -468,6 +184,12 @@ public static partial class Command
getDefaultValue: () => false
);
+ var disableGpgSigningOption = new Option(
+ name: "--disable-gpg-signing",
+ description: "Disables GPG signing of packages",
+ getDefaultValue: () => false
+ );
+
var command = new System.CommandLine.Command("build", "Builds the packages for a release") {
gitStashPushOption,
releaseChannelOption,
@@ -482,7 +204,8 @@ public static partial class Command
disableDockerPushOption,
dockerRepoOption,
changelogFileOption,
- disableNotarizeSigningOption
+ disableNotarizeSigningOption,
+ disableGpgSigningOption
};
command.Handler = CommandHandler.Create(DoBuild);
@@ -506,6 +229,7 @@ public static partial class Command
/// The docker repository to push to
/// The path to the changelog file
/// If notarize signing should be disabled
+ /// If GPG signing should be disabled
record CommandInput(
PackageTarget[] Targets,
DirectoryInfo BuildPath,
@@ -520,7 +244,8 @@ public static partial class Command
string MacOSAppName,
string DockerRepo,
FileInfo ChangelogFile,
- bool DisableNotarizeSigning
+ bool DisableNotarizeSigning,
+ bool DisableGpgSigning
);
static async Task DoBuild(CommandInput input)
@@ -612,6 +337,7 @@ public static partial class Command
rtcfg.ToggleAuthenticodeSigning();
rtcfg.ToggleSignCodeSigning();
rtcfg.ToggleNotarizeSigning();
+ rtcfg.ToggleGpgSigning();
await rtcfg.ToggleDockerBuild();
if (!rtcfg.UseDockerBuild)
@@ -688,10 +414,23 @@ public static partial class Command
})
);
- File.Move(Path.Combine(input.BuildPath.FullName, "packages", "autoupdate.manifest"), Path.Combine(input.BuildPath.FullName, "packages", "latest-v2.manifest"), true);
+ File.Move(
+ Path.Combine(input.BuildPath.FullName, "packages", "autoupdate.manifest"),
+ Path.Combine(input.BuildPath.FullName, "packages", "latest-v2.manifest"),
+ true
+ );
+
+ var files = builtPackages.Select(x => x.CreatedFile).Append("latest-v2.manifest").ToList();
+
+ // Create the GPG signatures for the files
+ if (rtcfg.UseGPGSigning)
+ {
+ Console.WriteLine("Creating GPG signatures ...");
+ await GpgSign.SignReleaseFiles(files, rtcfg);
+ }
Console.WriteLine("Build completed, uploading packages ...");
- var files = builtPackages.Select(x => x.CreatedFile).Append("latest-v2.manifest").ToArray();
+
Console.WriteLine("Upload completed, releasing packages ...");