示例#1
0
        private static void Publish()
        {
            if (Directory.Exists(BinDistDirectory))
            {
                IoHelpers.DeleteRecursivelyWithMagicDustAsync(BinDistDirectory).GetAwaiter().GetResult();
                Console.WriteLine($"Deleted {BinDistDirectory}");
            }

            using (var process = Process.Start(new ProcessStartInfo
            {
                FileName = "cmd",
                RedirectStandardInput = true,
                WorkingDirectory = GuiProjectDirectory
            }))
            {
                process.StandardInput.WriteLine("dotnet clean --configuration Release && exit");
                process.WaitForExit();
            }

            var guiBinReleaseDirectory     = Path.GetFullPath(Path.Combine(GuiProjectDirectory, "bin\\Release"));
            var libraryBinReleaseDirectory = Path.GetFullPath(Path.Combine(LibraryProjectDirectory, "bin\\Release"));

            if (Directory.Exists(guiBinReleaseDirectory))
            {
                IoHelpers.DeleteRecursivelyWithMagicDustAsync(guiBinReleaseDirectory).GetAwaiter().GetResult();
                Console.WriteLine($"Deleted {guiBinReleaseDirectory}");
            }
            if (Directory.Exists(libraryBinReleaseDirectory))
            {
                IoHelpers.DeleteRecursivelyWithMagicDustAsync(libraryBinReleaseDirectory).GetAwaiter().GetResult();
                Console.WriteLine($"Deleted {libraryBinReleaseDirectory}");
            }

            foreach (string target in Targets)
            {
                string publishedFolder         = Path.Combine(BinDistDirectory, target);
                string currentBinDistDirectory = publishedFolder;

                Console.WriteLine();
                Console.WriteLine($"{nameof(currentBinDistDirectory)}:\t{currentBinDistDirectory}");

                Console.WriteLine();
                if (!Directory.Exists(currentBinDistDirectory))
                {
                    Directory.CreateDirectory(currentBinDistDirectory);
                    Console.WriteLine($"Created {currentBinDistDirectory}");
                }

                using (var process = Process.Start(new ProcessStartInfo
                {
                    FileName = "dotnet",
                    Arguments = $"clean",
                    WorkingDirectory = GuiProjectDirectory
                }))
                {
                    process.WaitForExit();
                }

                // https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-publish?tabs=netcore21
                // -c|--configuration {Debug|Release}
                //		Defines the build configuration. The default value is Debug.
                // --force
                //		Forces all dependencies to be resolved even if the last restore was successful. Specifying this flag is the same as deleting the project.assets.json file.
                // -o|--output <OUTPUT_DIRECTORY>
                //		Specifies the path for the output directory.
                //		If not specified, it defaults to ./bin/[configuration]/[framework]/publish/ for a framework-dependent deployment or
                //		./bin/[configuration]/[framework]/[runtime]/publish/ for a self-contained deployment.
                //		If the path is relative, the output directory generated is relative to the project file location, not to the current working directory.
                // --self-contained
                //		Publishes the .NET Core runtime with your application so the runtime does not need to be installed on the target machine.
                //		If a runtime identifier is specified, its default value is true. For more information about the different deployment types, see .NET Core application deployment.
                // -r|--runtime <RUNTIME_IDENTIFIER>
                //		Publishes the application for a given runtime. This is used when creating a self-contained deployment (SCD).
                //		For a list of Runtime Identifiers (RIDs), see the RID catalog. Default is to publish a framework-dependent deployment (FDD).
                // --version-suffix <VERSION_SUFFIX>
                //		Defines the version suffix to replace the asterisk (*) in the version field of the project file.
                // https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-restore?tabs=netcore2x
                // --disable-parallel
                //		Disables restoring multiple projects in parallel.
                // --no-cache
                //		Specifies to not cache packages and HTTP requests.
                // https://github.com/dotnet/docs/issues/7568
                // /p:Version=1.2.3.4
                //		"dotnet publish" supports msbuild command line options like /p:Version=1.2.3.4
                using (var process = Process.Start(new ProcessStartInfo
                {
                    FileName = "dotnet",
                    Arguments = $"publish --configuration Release --force --output \"{currentBinDistDirectory}\" --self-contained true --runtime \"{target}\" /p:VersionPrefix={VersionPrefix} --disable-parallel --no-cache /p:DebugType=none /p:DebugSymbols=false /p:ErrorReport=none /p:DocumentationFile=\"\" /p:Deterministic=true",
                    WorkingDirectory = GuiProjectDirectory
                }))
                {
                    process.WaitForExit();
                }

                Tools.ClearSha512Tags(currentBinDistDirectory);
                //Tools.RemoveSosDocsUnix(currentBinDistDirectory);

                // Remove Tor binaries that are not relevant to the platform.
                var torFolder   = new DirectoryInfo(Path.Combine(currentBinDistDirectory, "TorDaemons"));
                var toNotRemove = "";
                if (target.StartsWith("win"))
                {
                    toNotRemove = "win";
                }
                else if (target.StartsWith("linux"))
                {
                    toNotRemove = "lin";
                }
                else if (target.StartsWith("osx"))
                {
                    toNotRemove = "osx";
                }

                foreach (var file in torFolder.EnumerateFiles())
                {
                    if (!file.Name.Contains("data", StringComparison.OrdinalIgnoreCase) && !file.Name.Contains(toNotRemove, StringComparison.OrdinalIgnoreCase))
                    {
                        File.Delete(file.FullName);
                    }
                }

                // Remove binaries that are not relevant to the platform.
                var binaryFolder = new DirectoryInfo(Path.Combine(currentBinDistDirectory, "Microservices", "Binaries"));

                foreach (var dir in binaryFolder.EnumerateDirectories())
                {
                    if (!dir.Name.Contains(toNotRemove, StringComparison.OrdinalIgnoreCase))
                    {
                        IoHelpers.DeleteRecursivelyWithMagicDustAsync(dir.FullName).GetAwaiter().GetResult();
                    }
                }

                // Rename the final exe.
                string oldExecutablePath;
                string newExecutablePath;
                if (target.StartsWith("win"))
                {
                    oldExecutablePath = Path.Combine(currentBinDistDirectory, "WalletWasabi.Gui.exe");
                    newExecutablePath = Path.Combine(currentBinDistDirectory, $"{ExecutableName}.exe");
                }
                else                 // Linux & OSX
                {
                    oldExecutablePath = Path.Combine(currentBinDistDirectory, "WalletWasabi.Gui");
                    newExecutablePath = Path.Combine(currentBinDistDirectory, ExecutableName);
                }
                File.Move(oldExecutablePath, newExecutablePath);

                long installedSizeKb = Tools.DirSize(new DirectoryInfo(publishedFolder)) / 1000;

                if (target.StartsWith("win"))
                {
                    var icoPath = Path.Combine(GuiProjectDirectory, "Assets", "WasabiLogo.ico");
                    using (var process = Process.Start(new ProcessStartInfo
                    {
                        FileName = "rcedit",                         // https://github.com/electron/rcedit/
                        Arguments = $"\"{newExecutablePath}\" --set-icon \"{icoPath}\" --set-file-version \"{VersionPrefix}\" --set-product-version \"{VersionPrefix}\" --set-version-string \"LegalCopyright\" \"MIT\" --set-version-string \"CompanyName\" \"zkSNACKs\" --set-version-string \"FileDescription\" \"Privacy focused, ZeroLink compliant Bitcoin wallet.\" --set-version-string \"ProductName\" \"Wasabi Wallet\"",
                        WorkingDirectory = currentBinDistDirectory
                    }))
                    {
                        process.WaitForExit();
                    }

                    var daemonExePath = newExecutablePath.Substring(0, newExecutablePath.Length - 4) + "d.exe";
                    File.Copy(newExecutablePath, daemonExePath);

                    // Do not open console.
                    if (!NSubsysUtil.ProcessFile(newExecutablePath))
                    {
                        Console.WriteLine("ERROR: Could not remove console from exe.");
                    }

                    // IF IT'S IN ONLYBINARIES MODE DON'T DO ANYTHING FANCY PACKAGING AFTER THIS!!!
                    if (OnlyBinaries)
                    {
                        continue;                         // In Windows build at this moment it does not matter though.
                    }
                }
                else if (target.StartsWith("osx"))
                {
                    // IF IT'S IN ONLYBINARIES MODE DON'T DO ANYTHING FANCY PACKAGING AFTER THIS!!!
                    if (OnlyBinaries)
                    {
                        continue;
                    }

                    var tempName = Path.Combine(BinDistDirectory, $"temp-{target}");
                    Directory.Move(currentBinDistDirectory, tempName);
                    currentBinDistDirectory = tempName;

                    string macWasabiAppDir = Path.Combine(publishedFolder, "Wasabi Wallet.App");                     // This should be lowercase .app, but MAC will prevent people from upgrading if we change it.
                    string macContentsDir  = Path.Combine(macWasabiAppDir, "Contents");
                    string newName         = Path.GetFullPath(Path.Combine(macContentsDir, "MacOS"));
                    Directory.CreateDirectory(macContentsDir);
                    Directory.Move(currentBinDistDirectory, newName);
                    currentBinDistDirectory = newName;

                    string resourcesDir = Path.Combine(macContentsDir, "Resources");
                    string infoFilePath = Path.Combine(macContentsDir, "Info.plist");

                    Directory.CreateDirectory(resourcesDir);
                    var iconPath = Path.Combine(GuiProjectDirectory, "Assets", "WasabiLogo.icns");
                    File.Copy(iconPath, Path.Combine(resourcesDir, "WasabiLogo.icns"));

                    string infoContent = $@"<?xml version=""1.0"" encoding=""UTF-8""?>
<!DOCTYPE plist PUBLIC ""-//Apple//DTD PLIST 1.0//EN"" ""http://www.apple.com/DTDs/PropertyList-1.0.dtd"">
<plist version = ""1.0"">
<dict>
	<key>LSMinimumSystemVersion</key>
	<string>10.12</string>

	<key>LSArchitecturePriority</key>
	<array>
		<string>x86_64</string>
	</array>

	<key>CFBundleIconFile</key>
	<string>WasabiLogo.icns</string>

	<key>CFBundlePackageType</key>
	<string>APPL</string>

	<key>CFBundleShortVersionString</key>
	<string>{VersionPrefix}</string>

	<key>CFBundleVersion</key>
	<string>{VersionPrefix}</string>

	<key>CFBundleExecutable</key>
	<string>{ExecutableName}</string>

	<key>CFBundleName</key>
	<string>Wasabi Wallet</string>

	<key>CFBundleIdentifier</key>
	<string>zksnacks.wasabiwallet</string>

	<key>NSHighResolutionCapable</key>
	<true/>

	<key>NSAppleScriptEnabled</key>
	<true/>

	<key>LSApplicationCategoryType</key>
	<string>public.app-category.finance</string>

	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
</dict>
</plist>
";
                    File.WriteAllText(infoFilePath, infoContent);

                    using (var process = Process.Start(new ProcessStartInfo
                    {
                        FileName = "cmd",
                        RedirectStandardInput = true,
                        WorkingDirectory = publishedFolder
                    }))
                    {
                        process.StandardInput.WriteLine($"wsl ln -s /Applications && exit");
                        process.WaitForExit();
                    }

                    //how to generate .DS_Store file - https://github.com/zkSNACKs/WalletWasabi/pull/928/commits/e38ed672dee25f6e45a3eb16584887cc6d48c4e6
                    var dmgContentDir = Path.Combine(PackagerProjectDirectory, "Content", "Osx");
                    IoHelpers.CopyFilesRecursively(new DirectoryInfo(dmgContentDir), new DirectoryInfo(publishedFolder));

                    string uncompressedDmgFileName = $"Wasabi-uncompressed.dmg";
                    string uncompressedDmgFilePath = Path.Combine(BinDistDirectory, uncompressedDmgFileName);
                    string dmgFileName             = $"Wasabi-{VersionPrefix}.dmg";
                    using (var process = Process.Start(new ProcessStartInfo
                    {
                        FileName = "cmd",
                        RedirectStandardInput = true,
                        WorkingDirectory = BinDistDirectory
                    }))
                    {
                        // http://www.nathancoulson.com/proj_cross_tools.php
                        // -D: Do not use deep directory relocation, and instead just pack them in the way we see them
                        // -V: Volume Label
                        // -no-pad: Do not pad the end by 150 sectors (300kb). As it is not a cd image, not required
                        // -apple -r: Creates a .dmg image
                        process.StandardInput.WriteLine($"wsl genisoimage -D -V \"Wasabi Wallet\" -no-pad -apple -r -dir-mode 755 -o \"{uncompressedDmgFileName}\" \"{new DirectoryInfo(publishedFolder).Name}\" && exit");
                        process.WaitForExit();
                    }
                    // cd ~
                    // git clone https://github.com/planetbeing/libdmg-hfsplus.git && cd libdmg-hfsplus
                    // https://github.com/planetbeing/libdmg-hfsplus/issues/14
                    // mkdir build && cd build
                    // sudo apt-get install zlib1g-dev
                    // cmake ..
                    // cd build
                    // sudo apt-get install libssl1.0-dev
                    // cmake ..
                    // cd ~/libdmg-hfsplus/build/
                    // make
                    using (var process = Process.Start(new ProcessStartInfo
                    {
                        FileName = "cmd",
                        RedirectStandardInput = true,
                        WorkingDirectory = BinDistDirectory
                    }))
                    {
                        process.StandardInput.WriteLine($"wsl ~/libdmg-hfsplus/build/dmg/./dmg dmg \"{uncompressedDmgFileName}\" \"{dmgFileName}\" && exit");
                        process.WaitForExit();
                    }

                    IoHelpers.DeleteRecursivelyWithMagicDustAsync(publishedFolder).GetAwaiter().GetResult();
                    File.Delete(uncompressedDmgFilePath);

                    IoHelpers.DeleteRecursivelyWithMagicDustAsync(publishedFolder).GetAwaiter().GetResult();
                    Console.WriteLine($"Deleted {publishedFolder}");
                }
                else if (target.StartsWith("linux"))
                {
                    // IF IT'S IN ONLYBINARIES MODE DON'T DO ANYTHING FANCY PACKAGING AFTER THIS!!!
                    if (OnlyBinaries)
                    {
                        continue;
                    }

                    Console.WriteLine("Create Linux .tar.gz");
                    if (!Directory.Exists(publishedFolder))
                    {
                        throw new Exception($"{publishedFolder} does not exist.");
                    }
                    var newFolderName = $"WasabiLinux-{VersionPrefix}";
                    var newFolderPath = Path.Combine(BinDistDirectory, newFolderName);
                    Directory.Move(publishedFolder, newFolderPath);
                    publishedFolder = newFolderPath;

                    var linuxPath = $"/mnt/c/{Tools.LinuxPath(BinDistDirectory.Replace("C:\\", ""))}";                     // We assume that it is on drive C:\.

                    var commands = new[]
                    {
                        "cd ~",
                        "sudo umount /mnt/c",
                        "sudo mount -t drvfs C: /mnt/c -o metadata",
                        $"cd {linuxPath}",
                        $"sudo find ./{newFolderName} -type f -exec chmod 644 {{}} \\;",
                        $"sudo find ./{newFolderName} -type f \\( -name 'wassabee' -o -name 'hwi' -o -name 'bitcoind' \\) -exec chmod +x {{}} \\;",
                        $"tar -pczvf {newFolderName}.tar.gz {newFolderName}"
                    };
                    string arguments = string.Join(" && ", commands);

                    using (var process = Process.Start(new ProcessStartInfo
                    {
                        FileName = "wsl",
                        Arguments = arguments,
                        RedirectStandardInput = true,
                        WorkingDirectory = BinDistDirectory
                    }))
                    {
                        process.WaitForExit();
                    }

                    Console.WriteLine("Create Linux .deb");

                    var debFolderRelativePath            = "deb";
                    var debFolderPath                    = Path.Combine(BinDistDirectory, debFolderRelativePath);
                    var linuxUsrLocalBinFolder           = "/usr/local/bin/";
                    var debUsrLocalBinFolderRelativePath = Path.Combine(debFolderRelativePath, "usr", "local", "bin");
                    var debUsrLocalBinFolderPath         = Path.Combine(BinDistDirectory, debUsrLocalBinFolderRelativePath);
                    Directory.CreateDirectory(debUsrLocalBinFolderPath);
                    var debUsrAppFolderRelativePath = Path.Combine(debFolderRelativePath, "usr", "share", "applications");
                    var debUsrAppFolderPath         = Path.Combine(BinDistDirectory, debUsrAppFolderRelativePath);
                    Directory.CreateDirectory(debUsrAppFolderPath);
                    var debUsrShareIconsFolderRelativePath = Path.Combine(debFolderRelativePath, "usr", "share", "icons", "hicolor");
                    var debUsrShareIconsFolderPath         = Path.Combine(BinDistDirectory, debUsrShareIconsFolderRelativePath);
                    var debianFolderRelativePath           = Path.Combine(debFolderRelativePath, "DEBIAN");
                    var debianFolderPath = Path.Combine(BinDistDirectory, debianFolderRelativePath);
                    Directory.CreateDirectory(debianFolderPath);
                    newFolderName = "wasabiwallet";
                    var linuxWasabiWalletFolder = Tools.LinuxPathCombine(linuxUsrLocalBinFolder, newFolderName);
                    var newFolderRelativePath   = Path.Combine(debUsrLocalBinFolderRelativePath, newFolderName);
                    newFolderPath = Path.Combine(BinDistDirectory, newFolderRelativePath);
                    Directory.Move(publishedFolder, newFolderPath);

                    var assetsFolder = Path.Combine(GuiProjectDirectory, "Assets");
                    var assetsInfo   = new DirectoryInfo(assetsFolder);

                    foreach (var file in assetsInfo.EnumerateFiles())
                    {
                        var number = file.Name.Split(new string[] { "WasabiLogo", ".png" }, StringSplitOptions.RemoveEmptyEntries);
                        if (number.Length == 1 && int.TryParse(number.First(), out int size))
                        {
                            string destFolder = Path.Combine(debUsrShareIconsFolderPath, $"{size}x{size}", "apps");
                            Directory.CreateDirectory(destFolder);
                            file.CopyTo(Path.Combine(destFolder, $"{ExecutableName}.png"));
                        }
                    }

                    var controlFilePath = Path.Combine(debianFolderPath, "control");
                    // License format does not yet work, but should work in the future, it's work in progress: https://bugs.launchpad.net/ubuntu/+source/software-center/+bug/435183
                    var controlFileContent = $"Package: {ExecutableName}\n" +
                                             $"Priority: optional\n" +
                                             $"Section: utils\n" +
                                             $"Maintainer: nopara73 <*****@*****.**>\n" +
                                             $"Version: {VersionPrefix}\n" +
                                             $"Homepage: http://wasabiwallet.io\n" +
                                             $"Vcs-Git: git://github.com/zkSNACKs/WalletWasabi.git\n" +
                                             $"Vcs-Browser: https://github.com/zkSNACKs/WalletWasabi\n" +
                                             $"Architecture: amd64\n" +
                                             $"License: Open Source (MIT)\n" +
                                             $"Installed-Size: {installedSizeKb}\n" +
                                             $"Description: open-source, non-custodial, privacy focused Bitcoin wallet\n" +
                                             $"  Built-in Tor, CoinJoin and Coin Control features.\n";

                    File.WriteAllText(controlFilePath, controlFileContent, Encoding.ASCII);

                    var desktopFilePath    = Path.Combine(debUsrAppFolderPath, $"{ExecutableName}.desktop");
                    var desktopFileContent = $"[Desktop Entry]\n" +
                                             $"Type=Application\n" +
                                             $"Name=Wasabi Wallet\n" +
                                             $"StartupWMClass=Wasabi Wallet\n" +
                                             $"GenericName=Bitcoin Wallet\n" +
                                             $"Comment=Privacy focused Bitcoin wallet.\n" +
                                             $"Icon={ExecutableName}\n" +
                                             $"Terminal=false\n" +
                                             $"Exec={ExecutableName}\n" +
                                             $"Categories=Office;Finance;\n" +
                                             $"Keywords=bitcoin;wallet;crypto;blockchain;wasabi;privacy;anon;awesome;qwe;asd;\n";

                    File.WriteAllText(desktopFilePath, desktopFileContent, Encoding.ASCII);

                    var wasabiStarterScriptPath    = Path.Combine(debUsrLocalBinFolderPath, $"{ExecutableName}");
                    var wasabiStarterScriptContent = $"#!/bin/sh\n" +
                                                     $"{ linuxWasabiWalletFolder.TrimEnd('/')}/{ExecutableName} $@\n";

                    File.WriteAllText(wasabiStarterScriptPath, wasabiStarterScriptContent, Encoding.ASCII);

                    string debExeLinuxPath        = Tools.LinuxPathCombine(newFolderRelativePath, ExecutableName);
                    string debDestopFileLinuxPath = Tools.LinuxPathCombine(debUsrAppFolderRelativePath, $"{ExecutableName}.desktop");

                    commands = new[]
                    {
                        "cd ~",
                        "sudo umount /mnt/c",
                        "sudo mount -t drvfs C: /mnt/c -o metadata",
                        $"cd {linuxPath}",
                        $"sudo find {Tools.LinuxPath(newFolderRelativePath)} -type f -exec chmod 644 {{}} \\;",
                        $"sudo find {Tools.LinuxPath(newFolderRelativePath)} -type f \\( -name 'wassabee' -o -name 'hwi' -o -name 'bitcoind' \\) -exec chmod +x {{}} \\;",
                        $"sudo chmod -R 0775 {Tools.LinuxPath(debianFolderRelativePath)}",
                        $"sudo chmod -R 0644 {debDestopFileLinuxPath}",
                        $"dpkg --build {Tools.LinuxPath(debFolderRelativePath)} $(pwd)"
                    };
                    arguments = string.Join(" && ", commands);
                    using (var process = Process.Start(new ProcessStartInfo
                    {
                        FileName = "wsl",
                        Arguments = arguments,
                        RedirectStandardInput = true,
                        WorkingDirectory = BinDistDirectory
                    }))
                    {
                        process.WaitForExit();
                    }

                    IoHelpers.DeleteRecursivelyWithMagicDustAsync(debFolderPath).GetAwaiter().GetResult();

                    string oldDeb = Path.Combine(BinDistDirectory, $"{ExecutableName}_{VersionPrefix}_amd64.deb");
                    string newDeb = Path.Combine(BinDistDirectory, $"Wasabi-{VersionPrefix}.deb");
                    File.Move(oldDeb, newDeb);

                    IoHelpers.DeleteRecursivelyWithMagicDustAsync(publishedFolder).GetAwaiter().GetResult();
                    Console.WriteLine($"Deleted {publishedFolder}");
                }
            }
        }
示例#2
0
    public static void Sign(ArgsProcessor argsProcessor)
    {
        if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
        {
            throw new NotSupportedException("This signing method is only valid on macOS!");
        }

        Console.WriteLine("Phase: finding the zip file on desktop which contains the compiled binaries from Windows.");

        string desktopPath          = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
        string removableDriveFolder = Tools.GetSingleUsbDrive();

        var srcZipFileNamePattern = "WasabiToNotarize-*";
        var files = Directory.GetFiles(removableDriveFolder, srcZipFileNamePattern);

        if (files.Length != 2)
        {
            throw new InvalidDataException($"{srcZipFileNamePattern} file missing or there are more! There must be exactly two!");
        }

        var(appleId, password) = argsProcessor.GetAppleIdAndPassword();

        while (string.IsNullOrWhiteSpace(appleId))
        {
            Console.WriteLine("Enter appleId (email):");
            appleId = Console.ReadLine();
        }

        while (string.IsNullOrWhiteSpace(password))
        {
            Console.WriteLine("Enter password:"******"WasabiToNotarize-2.0.0.0-arm64.zip or WasabiToNotarize-2.0.0.0.zip ".
            var workingDir          = Path.Combine(desktopPath, "wasabiTemp");
            var dmgPath             = Path.Combine(workingDir, "dmg");
            var unzippedPath        = Path.Combine(workingDir, "unzipped");
            var appName             = $"{Constants.AppName}.app";
            var appPath             = Path.Combine(dmgPath, appName);
            var appContentsPath     = Path.Combine(appPath, "Contents");
            var appMacOsPath        = Path.Combine(appContentsPath, "MacOS");
            var appResPath          = Path.Combine(appContentsPath, "Resources");
            var appFrameworksPath   = Path.Combine(appContentsPath, "Frameworks");
            var infoFilePath        = Path.Combine(appContentsPath, "Info.plist");
            var dmgFileName         = zipFile.Replace("WasabiToNotarize", "Wasabi").Replace("zip", "dmg");
            var dmgFilePath         = Path.Combine(workingDir, dmgFileName);
            var dmgUnzippedFilePath = Path.Combine(workingDir, $"Wasabi.tmp.dmg");
            var appNotarizeFilePath = Path.Combine(workingDir, $"Wasabi-{versionPrefix}.zip");
            var contentsPath        = Path.GetFullPath(Path.Combine(Program.PackagerProjectDirectory.Replace("\\", "//"), "Content", "Osx"));
            var entitlementsPath    = Path.Combine(contentsPath, "entitlements.plist");
            var dmgContentsDir      = Path.Combine(contentsPath, "Dmg");
            var desktopDmgFilePath  = Path.Combine(desktopPath, dmgFileName);

            var signArguments = $"--sign \"L233B2JQ68\" --verbose --force --options runtime --timestamp";

            Console.WriteLine("Phase: creating the working directory.");

            if (Directory.Exists(workingDir))
            {
                DeleteWithChmod(workingDir);
            }

            if (File.Exists(desktopDmgFilePath))
            {
                File.Delete(desktopDmgFilePath);
            }

            Console.WriteLine("Phase: creating the app.");

            IoHelpers.EnsureDirectoryExists(appResPath);
            IoHelpers.EnsureDirectoryExists(appMacOsPath);

            ZipFile.ExtractToDirectory(zipPath, appMacOsPath);             // Copy the binaries.

            IoHelpers.CopyFilesRecursively(new DirectoryInfo(Path.Combine(contentsPath, "App")), new DirectoryInfo(appPath));

            Console.WriteLine("Update the plist file with current information for example with version.");

            var    lines            = File.ReadAllLines(infoFilePath);
            string?bundleIdentifier = null;

            for (int i = 0; i < lines.Length; i++)
            {
                string line = lines[i];
                if (!line.TrimStart().StartsWith("<key>", StringComparison.InvariantCultureIgnoreCase))
                {
                    continue;
                }

                if (line.Contains("CFBundleShortVersionString", StringComparison.InvariantCulture) ||
                    line.Contains("CFBundleVersion", StringComparison.InvariantCulture))
                {
                    lines[i + 1] = lines[i + 1].Replace("?", $"{Version.Parse(versionPrefix).ToString(3)}");                     // Apple allow only 3 version tags in plist.
                }
                else if (line.Contains("CFBundleIdentifier", StringComparison.InvariantCulture))
                {
                    bundleIdentifier = lines[i + 1].Trim().Replace("<string>", "").Replace("</string>", "");
                }
            }
            if (string.IsNullOrWhiteSpace(bundleIdentifier))
            {
                throw new InvalidDataException("Bundle identifier not found in plist file.");
            }

            File.Delete(infoFilePath);

            File.WriteAllLines(infoFilePath, lines);

            using (var process = Process.Start(new ProcessStartInfo
            {
                FileName = "chmod",
                Arguments = $"-R u+rwX,go+rX,go-w \"{appPath}\"",
                WorkingDirectory = workingDir
            }))
            {
                WaitProcessToFinish(process, "chmod");
            }

            var filesToCheck = new[] { entitlementsPath };

            foreach (var file in filesToCheck)
            {
                if (!File.Exists(file))
                {
                    throw new FileNotFoundException($"File missing: {file}");
                }
            }

            Console.WriteLine("Signing the files in app.");

            IoHelpers.EnsureDirectoryExists(appResPath);
            IoHelpers.EnsureDirectoryExists(appMacOsPath);

            var executables = GetExecutables(appPath);

            // The main executable needs to be signed last.
            var filesToSignInOrder = Directory.GetFiles(appPath, "*.*", SearchOption.AllDirectories)
                                     .OrderBy(file => executables.Contains(file))
                                     .OrderBy(file => new FileInfo(file).Name == "wassabee")
                                     .ToArray();

            foreach (var file in executables)
            {
                using var process = Process.Start(new ProcessStartInfo
                {
                    FileName         = "chmod",
                    Arguments        = $"u+x \"{file}\"",
                    WorkingDirectory = workingDir
                });
                WaitProcessToFinish(process, "chmod");
            }

            SignDirectory(filesToSignInOrder, workingDir, signArguments, entitlementsPath);

            Console.WriteLine("Phase: verifying the signature.");

            Verify(appPath);

            Console.WriteLine("Phase: notarize the app.");

            // Source: https://blog.frostwire.com/2019/08/27/apple-notarization-the-signature-of-the-binary-is-invalid-one-other-reason-not-explained-in-apple-developer-documentation/
            using (var process = Process.Start(new ProcessStartInfo
            {
                FileName = "ditto",
                Arguments = $"-c -k --keepParent \"{appPath}\" \"{appNotarizeFilePath}\"",
                WorkingDirectory = workingDir
            }))
            {
                WaitProcessToFinish(process, "ditto");
            }

            Notarize(appleId, password, appNotarizeFilePath, bundleIdentifier);
            Staple(appPath);

            using (var process = Process.Start(new ProcessStartInfo
            {
                FileName = "spctl",
                Arguments = $"-a -t exec -vv \"{appPath}\"",
                WorkingDirectory = workingDir,
                RedirectStandardError = true
            }))
            {
                var    nonNullProcess = WaitProcessToFinish(process, "spctl");
                string result         = nonNullProcess.StandardError.ReadToEnd();
                if (!result.Contains(": accepted"))
                {
                    throw new InvalidOperationException(result);
                }
            }

            Console.WriteLine("Phase: creating the dmg.");

            if (File.Exists(dmgFilePath))
            {
                File.Delete(dmgFilePath);
            }

            Console.WriteLine("Phase: creating dmg.");

            IoHelpers.CopyFilesRecursively(new DirectoryInfo(dmgContentsDir), new DirectoryInfo(dmgPath));

            File.Copy(Path.Combine(contentsPath, "WasabiLogo.icns"), Path.Combine(dmgPath, ".VolumeIcon.icns"), true);

            var temp = Path.Combine(dmgPath, ".DS_Store.dat");
            File.Move(temp, Path.Combine(dmgPath, ".DS_Store"), true);

            using (var process = Process.Start(new ProcessStartInfo
            {
                FileName = "ln",
                Arguments = "-s /Applications",
                WorkingDirectory = dmgPath
            }))
            {
                WaitProcessToFinish(process, "ln");
            }

            var hdutilCreateArgs = string.Join(
                " ",
                new string[]
            {
                "create",
                $"\"{dmgUnzippedFilePath}\"",
                "-ov",
                $"-volname \"Wasabi Wallet\"",
                "-fs HFS+",
                $"-srcfolder \"{dmgPath}\""
            });

            using (var process = Process.Start(new ProcessStartInfo
            {
                FileName = "hdiutil",
                Arguments = hdutilCreateArgs,
                WorkingDirectory = dmgPath
            }))
            {
                WaitProcessToFinish(process, "hdiutil");
            }

            var hdutilConvertArgs = string.Join(
                " ",
                new string[]
            {
                "convert",
                $"\"{dmgUnzippedFilePath}\"",
                "-format UDZO",
                $"-o \"{dmgFilePath}\""
            });

            using (var process = Process.Start(new ProcessStartInfo
            {
                FileName = "hdiutil",
                Arguments = hdutilConvertArgs,
                WorkingDirectory = dmgPath
            }))
            {
                WaitProcessToFinish(process, "hdiutil");
            }

            Console.WriteLine("Phase: signing the dmg file.");

            SignFile($"{signArguments} --entitlements \"{entitlementsPath}\" \"{dmgFilePath}\"", dmgPath);

            Console.WriteLine("Phase: verifying the signature.");

            Verify(dmgFilePath);

            Console.WriteLine("Phase: notarize dmg");
            Notarize(appleId, password, dmgFilePath, bundleIdentifier);

            Console.WriteLine("Phase: staple dmp");
            Staple(dmgFilePath);

            using (var process = Process.Start(new ProcessStartInfo
            {
                FileName = "spctl",
                Arguments = $"-a -t open --context context:primary-signature -v \"{dmgFilePath}\"",
                WorkingDirectory = workingDir,
                RedirectStandardError = true
            }))
            {
                var    nonNullProcess = WaitProcessToFinish(process, "spctl");
                string result         = nonNullProcess.StandardError.ReadToEnd();
                if (!result.Contains(": accepted"))
                {
                    throw new InvalidOperationException(result);
                }
            }

            File.Move(dmgFilePath, desktopDmgFilePath);
            DeleteWithChmod(workingDir);

            Console.WriteLine("Phase: finish.");

            var toRemovableFilePath = Path.Combine(removableDriveFolder, Path.GetFileName(desktopDmgFilePath));
            File.Move(desktopDmgFilePath, toRemovableFilePath, true);

            if (File.Exists(zipPath))
            {
                File.Delete(zipPath);
            }
        }
    }
示例#3
0
        private static void Main(string[] args)
        {
            // 0. Dump Client version (or else wrong .msi will be created) - Helpers.Constants.ClientVersion
            // 1. Publish with Packager.
            // 2. Build WIX project with Release and x64 configuration.
            // 3. Sign with Packager, set restore true so the password won't be kept.
            var doPublish         = true;
            var doSign            = false;
            var doRestoreThisFile = false;
            var pfxPassword       = "******";

            string pfxPath = "C:\\digicert.pfx";
            string packagerProjectDirectory = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..\\..\\..\\"));
            string solutionDirectory        = Path.GetFullPath(Path.Combine(packagerProjectDirectory, "..\\"));
            string guiProjectDirectory      = Path.GetFullPath(Path.Combine(solutionDirectory, "WalletWasabi.Gui\\"));
            string libraryProjectDirectory  = Path.GetFullPath(Path.Combine(solutionDirectory, "WalletWasabi\\"));
            string wixProjectDirectory      = Path.GetFullPath(Path.Combine(solutionDirectory, "WalletWasabi.WindowsInstaller\\"));
            string binDistDirectory         = Path.GetFullPath(Path.Combine(guiProjectDirectory, "bin\\dist"));

            Console.WriteLine($"{nameof(solutionDirectory)}:\t\t{solutionDirectory}");
            Console.WriteLine($"{nameof(packagerProjectDirectory)}:\t{packagerProjectDirectory}");
            Console.WriteLine($"{nameof(guiProjectDirectory)}:\t\t{guiProjectDirectory}");
            Console.WriteLine($"{nameof(libraryProjectDirectory)}:\t\t{libraryProjectDirectory}");
            Console.WriteLine($"{nameof(wixProjectDirectory)}:\t\t{wixProjectDirectory}");
            Console.WriteLine($"{nameof(binDistDirectory)}:\t\t{binDistDirectory}");

            string versionPrefix  = Helpers.Constants.ClientVersion.ToString();
            string executableName = "wassabee";

            Console.WriteLine();
            Console.WriteLine($"{nameof(versionPrefix)}:\t\t\t{versionPrefix}");
            Console.WriteLine($"{nameof(executableName)}:\t\t\t{executableName}");

            // https://docs.microsoft.com/en-us/dotnet/articles/core/rid-catalog
            // BOTTLENECKS:
            // Tor - win-32, linux-32, osx-64
            // .NET Core - win-32, linux-64, osx-64
            // Avalonia - win7-32, linux-64, osx-64
            // We'll only support x64, if someone complains, we can come back to it.
            // For 32 bit Windows there needs to be a lot of WIX configuration to be done.
            var targets = new List <string>
            {
                "win7-x64",
                "linux-x64",
                "osx-x64"
            };

            Console.WriteLine();
            Console.Write($"{nameof(targets)}:\t\t\t");
            targets.ForEach(x =>
            {
                if (targets.Last() != x)
                {
                    Console.Write($"{x}, ");
                }
                else
                {
                    Console.Write(x);
                }
            });
            Console.WriteLine();

            if (doPublish)
            {
                if (Directory.Exists(binDistDirectory))
                {
                    IoHelpers.DeleteRecursivelyWithMagicDustAsync(binDistDirectory).GetAwaiter().GetResult();
                    Console.WriteLine($"Deleted {binDistDirectory}");
                }

                var psiBuild = new ProcessStartInfo
                {
                    FileName = "cmd",
                    RedirectStandardInput = true,
                    WorkingDirectory      = guiProjectDirectory
                };
                using (var pBuild = Process.Start(psiBuild))
                {
                    pBuild.StandardInput.WriteLine("dotnet clean --configuration Release && exit");
                    pBuild.WaitForExit();
                }

                var guiBinReleaseDirectory     = Path.GetFullPath(Path.Combine(guiProjectDirectory, "bin\\Release"));
                var libraryBinReleaseDirectory = Path.GetFullPath(Path.Combine(libraryProjectDirectory, "bin\\Release"));
                if (Directory.Exists(guiBinReleaseDirectory))
                {
                    IoHelpers.DeleteRecursivelyWithMagicDustAsync(guiBinReleaseDirectory).GetAwaiter().GetResult();
                    Console.WriteLine($"Deleted {guiBinReleaseDirectory}");
                }
                if (Directory.Exists(libraryBinReleaseDirectory))
                {
                    IoHelpers.DeleteRecursivelyWithMagicDustAsync(libraryBinReleaseDirectory).GetAwaiter().GetResult();
                    Console.WriteLine($"Deleted {libraryBinReleaseDirectory}");
                }

                foreach (string target in targets)
                {
                    string currentBinDistDirectory;
                    string publishedFolder = Path.Combine(binDistDirectory, target);
                    string macWasabiAppDir = Path.Combine(publishedFolder, "Wasabi Wallet.App");                     // This should be lowercase .app, but MAC will prevent people from upgrading if we change it.
                    string macContentsDir  = Path.Combine(macWasabiAppDir, "Contents");
                    if (target.StartsWith("osx"))
                    {
                        currentBinDistDirectory = Path.GetFullPath(Path.Combine(macContentsDir, "MacOS"));
                    }
                    else
                    {
                        currentBinDistDirectory = publishedFolder;
                    }
                    Console.WriteLine();
                    Console.WriteLine($"{nameof(currentBinDistDirectory)}:\t{currentBinDistDirectory}");

                    Console.WriteLine();
                    if (!Directory.Exists(currentBinDistDirectory))
                    {
                        Directory.CreateDirectory(currentBinDistDirectory);
                        Console.WriteLine($"Created {currentBinDistDirectory}");
                    }

                    var psiClean = new ProcessStartInfo
                    {
                        FileName         = "dotnet",
                        Arguments        = $"clean",
                        WorkingDirectory = guiProjectDirectory
                    };
                    using (var pClean = Process.Start(psiClean))
                    {
                        pClean.WaitForExit();
                    }

                    // https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-publish?tabs=netcore21
                    // -c|--configuration {Debug|Release}
                    //		Defines the build configuration. The default value is Debug.
                    // --force
                    //		Forces all dependencies to be resolved even if the last restore was successful. Specifying this flag is the same as deleting the project.assets.json file.
                    // -o|--output <OUTPUT_DIRECTORY>
                    //		Specifies the path for the output directory.
                    //		If not specified, it defaults to ./bin/[configuration]/[framework]/publish/ for a framework-dependent deployment or
                    //		./bin/[configuration]/[framework]/[runtime]/publish/ for a self-contained deployment.
                    //		If the path is relative, the output directory generated is relative to the project file location, not to the current working directory.
                    // --self-contained
                    //		Publishes the .NET Core runtime with your application so the runtime doesn't need to be installed on the target machine.
                    //		If a runtime identifier is specified, its default value is true. For more information about the different deployment types, see .NET Core application deployment.
                    // -r|--runtime <RUNTIME_IDENTIFIER>
                    //		Publishes the application for a given runtime. This is used when creating a self-contained deployment (SCD).
                    //		For a list of Runtime Identifiers (RIDs), see the RID catalog. Default is to publish a framework-dependent deployment (FDD).
                    // --version-suffix <VERSION_SUFFIX>
                    //		Defines the version suffix to replace the asterisk (*) in the version field of the project file.
                    // https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-restore?tabs=netcore2x
                    // --disable-parallel
                    //		Disables restoring multiple projects in parallel.
                    // --no-cache
                    //		Specifies to not cache packages and HTTP requests.
                    // https://github.com/dotnet/docs/issues/7568
                    // /p:Version=1.2.3.4
                    //		"dotnet publish" supports msbuild command line options like /p:Version=1.2.3.4
                    var psiPublish = new ProcessStartInfo
                    {
                        FileName         = "dotnet",
                        Arguments        = $"publish --configuration Release --force --output \"{currentBinDistDirectory}\" --self-contained true --runtime \"{target}\" /p:VersionPrefix={versionPrefix} --disable-parallel --no-cache",
                        WorkingDirectory = guiProjectDirectory
                    };
                    using (var pPublish = Process.Start(psiPublish))
                    {
                        pPublish.WaitForExit();
                    }

                    // Remove Tor binaries those are not relevant to the platform.
                    var torFolder   = new DirectoryInfo(Path.Combine(currentBinDistDirectory, "TorDaemons"));
                    var toNotremove = "";
                    if (target.StartsWith("win"))
                    {
                        toNotremove = "win";
                    }
                    else if (target.StartsWith("linux"))
                    {
                        toNotremove = "linux";
                    }
                    else if (target.StartsWith("osx"))
                    {
                        toNotremove = "osx";
                    }
                    foreach (var file in torFolder.EnumerateFiles())
                    {
                        if (!file.Name.Contains("data", StringComparison.OrdinalIgnoreCase) && !file.Name.Contains(toNotremove, StringComparison.OrdinalIgnoreCase))
                        {
                            File.Delete(file.FullName);
                        }
                    }

                    // Rename the final exe.
                    string oldExecutablePath;
                    string newExecutablePath;
                    if (target.StartsWith("win"))
                    {
                        oldExecutablePath = Path.Combine(currentBinDistDirectory, "WalletWasabi.Gui.exe");
                        newExecutablePath = Path.Combine(currentBinDistDirectory, $"{executableName}.exe");
                    }
                    else                     // Linux & OSX
                    {
                        oldExecutablePath = Path.Combine(currentBinDistDirectory, "WalletWasabi.Gui");
                        newExecutablePath = Path.Combine(currentBinDistDirectory, executableName);
                    }
                    File.Move(oldExecutablePath, newExecutablePath);

                    long installedSizeKb = DirSize(new DirectoryInfo(publishedFolder)) / 1000;

                    if (target.StartsWith("win"))
                    {
                        var psiEditbin = new ProcessStartInfo
                        {
                            FileName         = "editbin",
                            Arguments        = $"\"{newExecutablePath}\" /SUBSYSTEM:WINDOWS",
                            WorkingDirectory = currentBinDistDirectory
                        };
                        using (var pEditbin = Process.Start(psiEditbin))
                        {
                            pEditbin.WaitForExit();
                        }

                        var icoPath   = Path.Combine(guiProjectDirectory, "Assets", "WasabiLogo.ico");
                        var psiRcedit = new ProcessStartInfo
                        {
                            FileName         = "rcedit",
                            Arguments        = $"\"{newExecutablePath}\" --set-icon \"{icoPath}\" --set-file-version \"{versionPrefix}\" --set-product-version \"{versionPrefix}\" --set-version-string \"LegalCopyright\" \"MIT\" --set-version-string \"CompanyName\" \"zkSNACKs\" --set-version-string \"FileDescription\" \"Privacy focused, ZeroLink compliant Bitcoin wallet.\" --set-version-string \"ProductName\" \"Wasabi Wallet\"",
                            WorkingDirectory = currentBinDistDirectory
                        };
                        using (var pRcedit = Process.Start(psiRcedit))
                        {
                            pRcedit.WaitForExit();
                        }
                    }
                    else if (target.StartsWith("osx"))
                    {
                        string resourcesDir = Path.Combine(macContentsDir, "Resources");
                        string infoFilePath = Path.Combine(macContentsDir, "Info.plist");

                        Directory.CreateDirectory(resourcesDir);
                        var iconpath = Path.Combine(guiProjectDirectory, "Assets", "WasabiLogo.icns");
                        File.Copy(iconpath, Path.Combine(resourcesDir, "WasabiLogo.icns"));

                        string infoContent = $@"<?xml version=""1.0"" encoding=""UTF-8""?>
<!DOCTYPE plist PUBLIC ""-//Apple//DTD PLIST 1.0//EN"" ""http://www.apple.com/DTDs/PropertyList-1.0.dtd"">
<plist version = ""1.0"">
<dict>
	<key>LSMinimumSystemVersion</key>
	<string>10.12</string>

	<key>LSArchitecturePriority</key>
	<array>
		<string>x86_64</string>
	</array>

	<key>CFBundleIconFile</key>
	<string>WasabiLogo.icns</string>

	<key>CFBundlePackageType</key>
	<string>APPL</string>

	<key>CFBundleShortVersionString</key>
	<string>{versionPrefix}</string>

	<key>CFBundleVersion</key>
	<string>{versionPrefix}</string>

	<key>CFBundleExecutable</key>
	<string>{executableName}</string>

	<key>CFBundleName</key>
	<string>Wasabi Wallet</string>

	<key>CFBundleIdentifier</key>
	<string>zksnacks.wasabiwallet</string>

	<key>NSHighResolutionCapable</key>
	<true/>

	<key>NSAppleScriptEnabled</key>
	<true/>

	<key>LSApplicationCategoryType</key>
	<string>public.app-category.finance</string>

	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
</dict>
</plist>
";
                        File.WriteAllText(infoFilePath, infoContent);

                        var psiCreateSymLink = new ProcessStartInfo
                        {
                            FileName = "cmd",
                            RedirectStandardInput = true,
                            WorkingDirectory      = publishedFolder
                        };
                        using (var createSymLinkProcess = Process.Start(psiCreateSymLink))
                        {
                            createSymLinkProcess.StandardInput.WriteLine($"wsl ln -s /Applications && exit");
                            createSymLinkProcess.WaitForExit();
                        }

                        //how to generate .DS_Store file - https://github.com/zkSNACKs/WalletWasabi/pull/928/commits/e38ed672dee25f6e45a3eb16584887cc6d48c4e6
                        var dmgContentDir = Path.Combine(packagerProjectDirectory, "Content", "Osx");
                        IoHelpers.CopyFilesRecursively(new DirectoryInfo(dmgContentDir), new DirectoryInfo(publishedFolder));

                        var psiGenIsoImage = new ProcessStartInfo
                        {
                            FileName = "cmd",
                            RedirectStandardInput = true,
                            WorkingDirectory      = binDistDirectory
                        };

                        string uncompressedDmgFileName = $"Wasabi-uncompressed.dmg";
                        string uncompressedDmgFilePath = Path.Combine(binDistDirectory, uncompressedDmgFileName);
                        string dmgFileName             = $"Wasabi-{versionPrefix}.dmg";
                        using (var genIsoImageProcess = Process.Start(psiGenIsoImage))
                        {
                            // http://www.nathancoulson.com/proj_cross_tools.php
                            // -D: Do not use deep directory relocation, and instead just pack them in the way we see them
                            // -V: Volume Label
                            // -no-pad: Do not pad the end by 150 sectors (300kb). As it is not a cd image, not required
                            // -apple -r: Creates a .dmg image
                            genIsoImageProcess.StandardInput.WriteLine($"wsl genisoimage -D -V \"Wasabi Wallet\" -no-pad -apple -r -dir-mode 755 -o \"{uncompressedDmgFileName}\" \"{new DirectoryInfo(publishedFolder).Name}\" && exit");
                            genIsoImageProcess.WaitForExit();
                        }
                        // cd ~
                        // git clone https://github.com/planetbeing/libdmg-hfsplus.git && cd libdmg-hfsplus
                        // https://github.com/planetbeing/libdmg-hfsplus/issues/14
                        // mkdir build && cd build
                        // sudo apt-get install zlib1g-dev
                        // cmake ..
                        // cd build
                        // sudo apt-get install libssl1.0-dev
                        // cmake ..
                        // cd ~/libdmg-hfsplus/build/
                        // make
                        var psiDmg = new ProcessStartInfo
                        {
                            FileName = "cmd",
                            RedirectStandardInput = true,
                            WorkingDirectory      = binDistDirectory
                        };
                        using (var dmgProcess = Process.Start(psiDmg))
                        {
                            dmgProcess.StandardInput.WriteLine($"wsl ~/libdmg-hfsplus/build/dmg/./dmg dmg \"{uncompressedDmgFileName}\" \"{dmgFileName}\" && exit");
                            dmgProcess.WaitForExit();
                        }
                        // In case compression above doesn't work:
                        //var psiBzip = new ProcessStartInfo
                        //{
                        //	FileName = "cmd",
                        //	RedirectStandardInput = true,
                        //	WorkingDirectory = binDistDirectory
                        //};
                        //var bzipProcess = Process.Start(psiBzip);
                        //bzipProcess.StandardInput.WriteLine($"wsl bzip2 \"{uncompressedDmgFileName}\" && exit");
                        //bzipProcess.WaitForExit();

                        IoHelpers.DeleteRecursivelyWithMagicDustAsync(publishedFolder).GetAwaiter().GetResult();
                        File.Delete(uncompressedDmgFilePath);

                        IoHelpers.DeleteRecursivelyWithMagicDustAsync(publishedFolder).GetAwaiter().GetResult();
                        Console.WriteLine($"Deleted {publishedFolder}");
                    }
                    else if (target.StartsWith("linux"))
                    {
                        Console.WriteLine("Create Linux .tar.gz");
                        if (!Directory.Exists(publishedFolder))
                        {
                            throw new Exception($"{publishedFolder} doesn't exist.");
                        }
                        var newFolderName = $"WasabiLinux-{versionPrefix}";
                        var newFolderPath = Path.Combine(binDistDirectory, newFolderName);
                        Directory.Move(publishedFolder, newFolderPath);
                        publishedFolder = newFolderPath;

                        var psiTar = new ProcessStartInfo
                        {
                            FileName = "cmd",
                            RedirectStandardInput = true,
                            WorkingDirectory      = binDistDirectory
                        };
                        using (var tarProcess = Process.Start(psiTar))
                        {
                            tarProcess.StandardInput.WriteLine($"wsl tar -pczvf {newFolderName}.tar.gz {newFolderName} && exit");
                            tarProcess.WaitForExit();
                        }

                        Console.WriteLine("Create Linux .deb");

                        var debFolderRelativePath            = "deb";
                        var debFolderPath                    = Path.Combine(binDistDirectory, debFolderRelativePath);
                        var linuxUsrLocalBinFolder           = "/usr/local/bin/";
                        var debUsrLocalBinFolderRelativePath = Path.Combine(debFolderRelativePath, "usr", "local", "bin");
                        var debUsrLocalBinFolderPath         = Path.Combine(binDistDirectory, debUsrLocalBinFolderRelativePath);
                        Directory.CreateDirectory(debUsrLocalBinFolderPath);
                        var debUsrAppFolderRelativePath = Path.Combine(debFolderRelativePath, "usr", "share", "applications");
                        var debUsrAppFolderPath         = Path.Combine(binDistDirectory, debUsrAppFolderRelativePath);
                        Directory.CreateDirectory(debUsrAppFolderPath);
                        var debUsrShareIconsFolderRelativePath = Path.Combine(debFolderRelativePath, "usr", "share", "icons", "hicolor");
                        var debUsrShareIconsFolderPath         = Path.Combine(binDistDirectory, debUsrShareIconsFolderRelativePath);
                        var debianFolderRelativePath           = Path.Combine(debFolderRelativePath, "DEBIAN");
                        var debianFolderPath = Path.Combine(binDistDirectory, debianFolderRelativePath);
                        Directory.CreateDirectory(debianFolderPath);
                        newFolderName = "wasabiwallet";
                        var linuxWasabiWalletFolder = LinuxPathCombine(linuxUsrLocalBinFolder, newFolderName);
                        var newFolderRelativePath   = Path.Combine(debUsrLocalBinFolderRelativePath, newFolderName);
                        newFolderPath = Path.Combine(binDistDirectory, newFolderRelativePath);
                        Directory.Move(publishedFolder, newFolderPath);

                        var assetsFolder = Path.Combine(guiProjectDirectory, "Assets");
                        var assetsInfo   = new DirectoryInfo(assetsFolder);

                        foreach (var file in assetsInfo.EnumerateFiles())
                        {
                            var number = file.Name.Split(new string[] { "WasabiLogo", ".png" }, StringSplitOptions.RemoveEmptyEntries);
                            if (number.Count() == 1 && int.TryParse(number.First(), out int size))
                            {
                                string destFolder = Path.Combine(debUsrShareIconsFolderPath, $"{size}x{size}", "apps");
                                Directory.CreateDirectory(destFolder);
                                file.CopyTo(Path.Combine(destFolder, $"{executableName}.png"));
                            }
                        }

                        var controlFilePath = Path.Combine(debianFolderPath, "control");
                        // License format doesn't yet work, but should work in the future, it's work in progress: https://bugs.launchpad.net/ubuntu/+source/software-center/+bug/435183
                        var controlFileContent = $"Package: {executableName}\n" +
                                                 $"Priority: optional\n" +
                                                 $"Section: utils\n" +
                                                 $"Maintainer: nopara73 <*****@*****.**>\n" +
                                                 $"Version: {versionPrefix}\n" +
                                                 $"Homepage: http://wasabiwallet.io\n" +
                                                 $"Vcs-Git: git://github.com/zkSNACKs/WalletWasabi.git\n" +
                                                 $"Vcs-Browser: https://github.com/zkSNACKs/WalletWasabi\n" +
                                                 $"Architecture: amd64\n" +
                                                 $"License: Open Source (MIT)\n" +
                                                 $"Installed-Size: {installedSizeKb}\n" +
                                                 $"Description: open-source, non-custodial, privacy focused Bitcoin wallet\n" +
                                                 $"  Built-in Tor, CoinJoin and Coin Control features.\n";

                        File.WriteAllText(controlFilePath, controlFileContent, Encoding.ASCII);

                        var desktopFilePath    = Path.Combine(debUsrAppFolderPath, $"{executableName}.desktop");
                        var desktopFileContent = $"[Desktop Entry]\n" +
                                                 $"Type=Application\n" +
                                                 $"Name=Wasabi Wallet\n" +
                                                 $"GenericName=Bitcoin Wallet\n" +
                                                 $"Comment=Privacy focused Bitcoin wallet.\n" +
                                                 $"Icon={executableName}\n" +
                                                 $"Terminal=false\n" +
                                                 $"Exec={executableName}\n" +
                                                 $"Categories=Office;Finance;\n" +
                                                 $"Keywords=bitcoin;wallet;crypto;blockchain;wasabi;privacy;anon;awesome;qwe;asd;\n";

                        File.WriteAllText(desktopFilePath, desktopFileContent, Encoding.ASCII);

                        var wasabiStarterScriptPath    = Path.Combine(debUsrLocalBinFolderPath, $"{executableName}");
                        var wasabiStarterScriptContent = $"#!/bin/sh\n" +
                                                         $"{ linuxWasabiWalletFolder.TrimEnd('/')}/./{executableName}\n";

                        File.WriteAllText(wasabiStarterScriptPath, wasabiStarterScriptContent, Encoding.ASCII);

                        string debExeLinuxPath              = LinuxPathCombine(newFolderRelativePath, executableName);
                        string debDestopFileLinuxPath       = LinuxPathCombine(debUsrAppFolderRelativePath, $"{executableName}.desktop");
                        var    wasabiStarterScriptLinuxPath = LinuxPathCombine(debUsrLocalBinFolderRelativePath, $"{executableName}");
                        var    psi = new ProcessStartInfo
                        {
                            FileName              = "wsl",
                            Arguments             = $"cd ~ && sudo umount /mnt/c && sudo mount -t drvfs C: /mnt/c -o metadata && cd /mnt/c/Users/user/Desktop/WalletWasabi/WalletWasabi.Gui/bin/dist && sudo chmod +x {debExeLinuxPath} && sudo chmod +x {wasabiStarterScriptLinuxPath} && sudo chmod -R 0644 {debDestopFileLinuxPath} && sudo chmod -R 0775 {LinuxPath(debianFolderRelativePath)} && dpkg --build {LinuxPath(debFolderRelativePath)} $(pwd)",
                            RedirectStandardInput = true,
                            WorkingDirectory      = binDistDirectory
                        };

                        using (var p = Process.Start(psi))
                        {
                            p.WaitForExit();
                        }

                        IoHelpers.DeleteRecursivelyWithMagicDustAsync(debFolderPath).GetAwaiter().GetResult();

                        string oldDeb = Path.Combine(binDistDirectory, $"{executableName}_{versionPrefix}_amd64.deb");
                        string newDeb = Path.Combine(binDistDirectory, $"Wasabi-{versionPrefix}.deb");
                        File.Move(oldDeb, newDeb);

                        IoHelpers.DeleteRecursivelyWithMagicDustAsync(publishedFolder).GetAwaiter().GetResult();
                        Console.WriteLine($"Deleted {publishedFolder}");
                    }
                }
            }

            if (doSign is true)
            {
                foreach (string target in targets)
                {
                    if (target.StartsWith("win", StringComparison.OrdinalIgnoreCase))
                    {
                        string publishedFolder = Path.Combine(binDistDirectory, target);

                        Console.WriteLine("Move created .msi");
                        var msiPath = Path.Combine(wixProjectDirectory, @"bin\Release\Wasabi.msi");
                        if (!File.Exists(msiPath))
                        {
                            throw new Exception(".msi doesn't exist. Expected path: Wasabi.msi.");
                        }
                        var msiFileName = Path.GetFileNameWithoutExtension(msiPath);
                        var newMsiPath  = Path.Combine(binDistDirectory, $"{msiFileName}-{versionPrefix}.msi");
                        File.Move(msiPath, newMsiPath);

                        // Sign code with digicert.
                        var psiSigntool = new ProcessStartInfo
                        {
                            FileName = "cmd",
                            RedirectStandardInput = true,
                            WorkingDirectory      = binDistDirectory
                        };
                        using (var signToolProcess = Process.Start(psiSigntool))
                        {
                            signToolProcess.StandardInput.WriteLine($"signtool sign /d \"Wasabi Wallet\" /f \"{pfxPath}\" /p {pfxPassword} /t http://timestamp.digicert.com /a \"{newMsiPath}\" && exit");
                            signToolProcess.WaitForExit();
                        }

                        IoHelpers.DeleteRecursivelyWithMagicDustAsync(publishedFolder).GetAwaiter().GetResult();
                        Console.WriteLine($"Deleted {publishedFolder}");
                    }
                }

                Console.WriteLine("Signing final files...");
                var finalFiles = Directory.GetFiles(binDistDirectory);

                foreach (var finalFile in finalFiles)
                {
                    var psiSignProcess = new ProcessStartInfo
                    {
                        FileName = "cmd",
                        RedirectStandardInput = true,
                        WorkingDirectory      = binDistDirectory
                    };
                    using (var signProcess = Process.Start(psiSignProcess))
                    {
                        signProcess.StandardInput.WriteLine($"gpg --armor --detach-sign {finalFile} && exit");
                        signProcess.WaitForExit();
                    }

                    var psiRestoreHeat = new ProcessStartInfo
                    {
                        FileName = "cmd",
                        RedirectStandardInput = true,
                        WorkingDirectory      = wixProjectDirectory
                    };
                    using (var restoreHeatProcess = Process.Start(psiRestoreHeat))
                    {
                        restoreHeatProcess.StandardInput.WriteLine($"git checkout -- ComponentsGenerated.wxs && exit");
                        restoreHeatProcess.WaitForExit();
                    }

                    if (doRestoreThisFile)
                    {
                        var psiRestoreThisFile = new ProcessStartInfo
                        {
                            FileName = "cmd",
                            RedirectStandardInput = true,
                            WorkingDirectory      = packagerProjectDirectory
                        };
                        using (var restoreThisFileProcess = Process.Start(psiRestoreThisFile))
                        {
                            restoreThisFileProcess.StandardInput.WriteLine($"git checkout -- Program.cs && exit");
                            restoreThisFileProcess.WaitForExit();
                        }
                    }
                }

                IoHelpers.OpenFolderInFileExplorer(binDistDirectory);
                return;                 // No need for readkey here.
            }
        }
示例#4
0
        private static void Main(string[] args)
        {
            // 0. Dump Client version (or else wrong .msi will be created) - Helpers.Constants.ClientVersion
            // 1. Publish with Packager.
            // 2. Build WIX project with Release and x64 configuration.
            // 3. Sign with Packager, set restore true so the password won't be kept.
            var doPublish         = true;
            var doSign            = false;
            var doRestoreThisFile = false;
            var pfxPassword       = "******";

            string pfxPath = "C:\\digicert.pfx";
            string packagerProjectDirectory = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..\\..\\..\\"));
            string solutionDirectory        = Path.GetFullPath(Path.Combine(packagerProjectDirectory, "..\\"));
            string guiProjectDirectory      = Path.GetFullPath(Path.Combine(solutionDirectory, "WalletWasabi.Gui\\"));
            string libraryProjectDirectory  = Path.GetFullPath(Path.Combine(solutionDirectory, "WalletWasabi\\"));
            string wixProjectDirectory      = Path.GetFullPath(Path.Combine(solutionDirectory, "WalletWasabi.WindowsInstaller\\"));
            string binDistDirectory         = Path.GetFullPath(Path.Combine(guiProjectDirectory, "bin\\dist"));

            Console.WriteLine($"{nameof(solutionDirectory)}:\t\t{solutionDirectory}");
            Console.WriteLine($"{nameof(packagerProjectDirectory)}:\t{packagerProjectDirectory}");
            Console.WriteLine($"{nameof(guiProjectDirectory)}:\t\t{guiProjectDirectory}");
            Console.WriteLine($"{nameof(libraryProjectDirectory)}:\t\t{libraryProjectDirectory}");
            Console.WriteLine($"{nameof(wixProjectDirectory)}:\t\t{wixProjectDirectory}");
            Console.WriteLine($"{nameof(binDistDirectory)}:\t\t{binDistDirectory}");

            string versionPrefix  = Helpers.Constants.ClientVersion.ToString();
            string executableName = "wassabee";

            Console.WriteLine();
            Console.WriteLine($"{nameof(versionPrefix)}:\t\t\t{versionPrefix}");
            Console.WriteLine($"{nameof(executableName)}:\t\t\t{executableName}");

            // https://docs.microsoft.com/en-us/dotnet/articles/core/rid-catalog
            // BOTTLENECKS:
            // Tor - win-32, linux-32, osx-64
            // .NET Core - win-32, linux-64, osx-64
            // Avalonia - win7-32, linux-64, osx-64
            // We'll only support x64, if someone complains, we can come back to it.
            // For 32 bit Windows there needs to be a lot of WIX configuration to be done.
            var targets = new List <string>
            {
                "win7-x64",
                "linux-x64",
                "osx-x64"
            };

            Console.WriteLine();
            Console.Write($"{nameof(targets)}:\t\t\t");
            targets.ForEach(x =>
            {
                if (targets.Last() != x)
                {
                    Console.Write($"{x}, ");
                }
                else
                {
                    Console.Write(x);
                }
            });
            Console.WriteLine();

            if (doPublish)
            {
                if (Directory.Exists(binDistDirectory))
                {
                    IoHelpers.DeleteRecursivelyWithMagicDustAsync(binDistDirectory).GetAwaiter().GetResult();
                    Console.WriteLine($"Deleted {binDistDirectory}");
                }

                var psiBuild = new ProcessStartInfo
                {
                    FileName = "cmd",
                    RedirectStandardInput = true,
                    WorkingDirectory      = guiProjectDirectory
                };
                using (var pBuild = Process.Start(psiBuild))
                {
                    pBuild.StandardInput.WriteLine("dotnet clean --configuration Release && exit");
                    pBuild.WaitForExit();
                }

                var guiBinReleaseDirectory     = Path.GetFullPath(Path.Combine(guiProjectDirectory, "bin\\Release"));
                var libraryBinReleaseDirectory = Path.GetFullPath(Path.Combine(libraryProjectDirectory, "bin\\Release"));
                if (Directory.Exists(guiBinReleaseDirectory))
                {
                    IoHelpers.DeleteRecursivelyWithMagicDustAsync(guiBinReleaseDirectory).GetAwaiter().GetResult();
                    Console.WriteLine($"Deleted {guiBinReleaseDirectory}");
                }
                if (Directory.Exists(libraryBinReleaseDirectory))
                {
                    IoHelpers.DeleteRecursivelyWithMagicDustAsync(libraryBinReleaseDirectory).GetAwaiter().GetResult();
                    Console.WriteLine($"Deleted {libraryBinReleaseDirectory}");
                }

                foreach (string target in targets)
                {
                    string currentBinDistDirectory;
                    string publishedFolder = Path.Combine(binDistDirectory, target);
                    string macWasabiAppDir = Path.Combine(publishedFolder, "Wasabi Wallet.App");
                    string macContentsDir  = Path.Combine(macWasabiAppDir, "Contents");
                    if (target.StartsWith("osx"))
                    {
                        currentBinDistDirectory = Path.GetFullPath(Path.Combine(macContentsDir, "MacOS"));
                    }
                    else
                    {
                        currentBinDistDirectory = publishedFolder;
                    }
                    Console.WriteLine();
                    Console.WriteLine($"{nameof(currentBinDistDirectory)}:\t{currentBinDistDirectory}");

                    Console.WriteLine();
                    if (!Directory.Exists(currentBinDistDirectory))
                    {
                        Directory.CreateDirectory(currentBinDistDirectory);
                        Console.WriteLine($"Created {currentBinDistDirectory}");
                    }

                    var psiClean = new ProcessStartInfo
                    {
                        FileName         = "dotnet",
                        Arguments        = $"clean",
                        WorkingDirectory = guiProjectDirectory
                    };
                    using (var pClean = Process.Start(psiClean))
                    {
                        pClean.WaitForExit();
                    }

                    // https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-publish?tabs=netcore21
                    // -c|--configuration {Debug|Release}
                    //		Defines the build configuration. The default value is Debug.
                    // --force
                    //		Forces all dependencies to be resolved even if the last restore was successful. Specifying this flag is the same as deleting the project.assets.json file.
                    // -o|--output <OUTPUT_DIRECTORY>
                    //		Specifies the path for the output directory.
                    //		If not specified, it defaults to ./bin/[configuration]/[framework]/publish/ for a framework-dependent deployment or
                    //		./bin/[configuration]/[framework]/[runtime]/publish/ for a self-contained deployment.
                    //		If the path is relative, the output directory generated is relative to the project file location, not to the current working directory.
                    // --self-contained
                    //		Publishes the .NET Core runtime with your application so the runtime doesn't need to be installed on the target machine.
                    //		If a runtime identifier is specified, its default value is true. For more information about the different deployment types, see .NET Core application deployment.
                    // -r|--runtime <RUNTIME_IDENTIFIER>
                    //		Publishes the application for a given runtime. This is used when creating a self-contained deployment (SCD).
                    //		For a list of Runtime Identifiers (RIDs), see the RID catalog. Default is to publish a framework-dependent deployment (FDD).
                    // --version-suffix <VERSION_SUFFIX>
                    //		Defines the version suffix to replace the asterisk (*) in the version field of the project file.
                    // https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-restore?tabs=netcore2x
                    // --disable-parallel
                    //		Disables restoring multiple projects in parallel.
                    // --no-cache
                    //		Specifies to not cache packages and HTTP requests.
                    // https://github.com/dotnet/docs/issues/7568
                    // /p:Version=1.2.3.4
                    //		"dotnet publish" supports msbuild command line options like /p:Version=1.2.3.4
                    var psiPublish = new ProcessStartInfo
                    {
                        FileName         = "dotnet",
                        Arguments        = $"publish --configuration Release --force --output \"{currentBinDistDirectory}\" --self-contained true --runtime \"{target}\" /p:VersionPrefix={versionPrefix} --disable-parallel --no-cache",
                        WorkingDirectory = guiProjectDirectory
                    };
                    using (var pPublish = Process.Start(psiPublish))
                    {
                        pPublish.WaitForExit();
                    }

                    // Rename the final exe.
                    string oldExecutablePath;
                    string newExecutablePath;
                    if (target.StartsWith("win"))
                    {
                        oldExecutablePath = Path.Combine(currentBinDistDirectory, "WalletWasabi.Gui.exe");
                        newExecutablePath = Path.Combine(currentBinDistDirectory, $"{executableName}.exe");
                    }
                    else                     // Linux & OSX
                    {
                        oldExecutablePath = Path.Combine(currentBinDistDirectory, "WalletWasabi.Gui");
                        newExecutablePath = Path.Combine(currentBinDistDirectory, executableName);
                    }
                    File.Move(oldExecutablePath, newExecutablePath);

                    if (target.StartsWith("win"))
                    {
                        var psiEditbin = new ProcessStartInfo
                        {
                            FileName         = "editbin",
                            Arguments        = $"\"{newExecutablePath}\" /SUBSYSTEM:WINDOWS",
                            WorkingDirectory = currentBinDistDirectory
                        };
                        using (var pEditbin = Process.Start(psiEditbin))
                        {
                            pEditbin.WaitForExit();
                        }

                        var icoPath   = Path.Combine(guiProjectDirectory, "Assets", "WasabiLogo.ico");
                        var psiRcedit = new ProcessStartInfo
                        {
                            FileName         = "rcedit",
                            Arguments        = $"\"{newExecutablePath}\" --set-icon \"{icoPath}\" --set-file-version \"{versionPrefix}\" --set-product-version \"{versionPrefix}\" --set-version-string \"LegalCopyright\" \"MIT\" --set-version-string \"CompanyName\" \"zkSNACKs\" --set-version-string \"FileDescription\" \"Privacy focused, ZeroLink compliant Bitcoin wallet.\" --set-version-string \"ProductName\" \"Wasabi Wallet\"",
                            WorkingDirectory = currentBinDistDirectory
                        };
                        using (var pRcedit = Process.Start(psiRcedit))
                        {
                            pRcedit.WaitForExit();
                        }
                    }
                    else if (target.StartsWith("osx"))
                    {
                        string resourcesDir = Path.Combine(macContentsDir, "Resources");
                        string infoFilePath = Path.Combine(macContentsDir, "Info.plist");

                        Directory.CreateDirectory(resourcesDir);
                        var iconpath = Path.Combine(guiProjectDirectory, "Assets", "WasabiLogo.icns");
                        File.Copy(iconpath, Path.Combine(resourcesDir, "WasabiLogo.icns"));

                        string infoContent = $@"<?xml version=""1.0"" encoding=""UTF-8""?>
<!DOCTYPE plist PUBLIC ""-//Apple//DTD PLIST 1.0//EN"" ""http://www.apple.com/DTDs/PropertyList-1.0.dtd"">
<plist version = ""1.0"">
<dict>
	<key>LSMinimumSystemVersion</key>
	<string>10.12</string>

	<key>LSArchitecturePriority</key>
	<array>
		<string>x86_64</string>
	</array>

	<key>CFBundleIconFile</key>
	<string>WasabiLogo.icns</string>

	<key>CFBundlePackageType</key>
	<string>APPL</string>

	<key>CFBundleShortVersionString</key>
	<string>{versionPrefix}</string>

	<key>CFBundleVersion</key>
	<string>{versionPrefix}</string>

	<key>CFBundleExecutable</key>
	<string>wassabee</string>

	<key>CFBundleName</key>
	<string>Wasabi Wallet</string>

	<key>CFBundleIdentifier</key>
	<string>zksnacks.wasabiwallet</string>

	<key>NSHighResolutionCapable</key>
	<true/>

	<key>NSAppleScriptEnabled</key>
	<true/>

	<key>LSApplicationCategoryType</key>
	<string>public.app-category.finance</string>

	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
</dict>
</plist>
";
                        File.WriteAllText(infoFilePath, infoContent);

                        var psiCreateSymLink = new ProcessStartInfo
                        {
                            FileName = "cmd",
                            RedirectStandardInput = true,
                            WorkingDirectory      = publishedFolder
                        };
                        using (var createSymLinkProcess = Process.Start(psiCreateSymLink))
                        {
                            createSymLinkProcess.StandardInput.WriteLine($"wsl ln -s /Applications && exit");
                            createSymLinkProcess.WaitForExit();
                        }

                        //how to generate .DS_Store file - https://github.com/zkSNACKs/WalletWasabi/pull/928/commits/e38ed672dee25f6e45a3eb16584887cc6d48c4e6
                        var dmgContentDir = Path.Combine(packagerProjectDirectory, "Content", "Osx");
                        IoHelpers.CopyFilesRecursively(new DirectoryInfo(dmgContentDir), new DirectoryInfo(publishedFolder));

                        var psiGenIsoImage = new ProcessStartInfo
                        {
                            FileName = "cmd",
                            RedirectStandardInput = true,
                            WorkingDirectory      = binDistDirectory
                        };

                        string uncompressedDmgFileName = $"Wasabi-uncompressed.dmg";
                        string uncompressedDmgFilePath = Path.Combine(binDistDirectory, uncompressedDmgFileName);
                        string dmgFileName             = $"Wasabi-{versionPrefix}.dmg";
                        using (var genIsoImageProcess = Process.Start(psiGenIsoImage))
                        {
                            // http://www.nathancoulson.com/proj_cross_tools.php
                            // -D: Do not use deep directory relocation, and instead just pack them in the way we see them
                            // -V: Volume Label
                            // -no-pad: Do not pad the end by 150 sectors (300kb). As it is not a cd image, not required
                            // -apple -r: Creates a .dmg image
                            genIsoImageProcess.StandardInput.WriteLine($"wsl genisoimage -D -V \"Wasabi Wallet\" -no-pad -apple -r -o \"{uncompressedDmgFileName}\" \"{new DirectoryInfo(publishedFolder).Name}\" && exit");
                            genIsoImageProcess.WaitForExit();
                        }
                        // cd ~
                        // git clone https://github.com/planetbeing/libdmg-hfsplus.git && cd libdmg-hfsplus
                        // https://github.com/planetbeing/libdmg-hfsplus/issues/14
                        // mkdir build && cd build
                        // sudo apt-get install zlib1g-dev
                        // cmake ..
                        // cd build
                        // sudo apt-get install libssl1.0-dev
                        // cmake ..
                        // cd ~/libdmg-hfsplus/build/
                        // make
                        var psiDmg = new ProcessStartInfo
                        {
                            FileName = "cmd",
                            RedirectStandardInput = true,
                            WorkingDirectory      = binDistDirectory
                        };
                        using (var dmgProcess = Process.Start(psiDmg))
                        {
                            dmgProcess.StandardInput.WriteLine($"wsl ~/libdmg-hfsplus/build/dmg/./dmg dmg \"{uncompressedDmgFileName}\" \"{dmgFileName}\" && exit");
                            dmgProcess.WaitForExit();
                        }
                        // In case compression above doesn't work:
                        //var psiBzip = new ProcessStartInfo
                        //{
                        //	FileName = "cmd",
                        //	RedirectStandardInput = true,
                        //	WorkingDirectory = binDistDirectory
                        //};
                        //var bzipProcess = Process.Start(psiBzip);
                        //bzipProcess.StandardInput.WriteLine($"wsl bzip2 \"{uncompressedDmgFileName}\" && exit");
                        //bzipProcess.WaitForExit();

                        IoHelpers.DeleteRecursivelyWithMagicDustAsync(publishedFolder).GetAwaiter().GetResult();
                        File.Delete(uncompressedDmgFilePath);

                        IoHelpers.DeleteRecursivelyWithMagicDustAsync(publishedFolder).GetAwaiter().GetResult();
                        Console.WriteLine($"Deleted {publishedFolder}");
                    }
                    else if (target.StartsWith("linux"))
                    {
                        Console.WriteLine("Create Linux .tar.gz");
                        if (!Directory.Exists(publishedFolder))
                        {
                            throw new Exception($"{publishedFolder} doesn't exist.");
                        }
                        var newFolderName = $"WasabiLinux-{versionPrefix}";
                        var newFolderPath = Path.Combine(binDistDirectory, newFolderName);
                        Directory.Move(publishedFolder, newFolderPath);
                        publishedFolder = newFolderPath;

                        var psiTar = new ProcessStartInfo
                        {
                            FileName = "cmd",
                            RedirectStandardInput = true,
                            WorkingDirectory      = binDistDirectory
                        };
                        using (var tarProcess = Process.Start(psiTar))
                        {
                            tarProcess.StandardInput.WriteLine($"wsl tar -pczvf {newFolderName}.tar.gz {newFolderName} && exit");
                            tarProcess.WaitForExit();
                        }

                        IoHelpers.DeleteRecursivelyWithMagicDustAsync(publishedFolder).GetAwaiter().GetResult();
                        Console.WriteLine($"Deleted {publishedFolder}");
                    }
                }
            }

            if (doSign is true)
            {
                foreach (string target in targets)
                {
                    if (target.StartsWith("win", StringComparison.OrdinalIgnoreCase))
                    {
                        string publishedFolder = Path.Combine(binDistDirectory, target);

                        Console.WriteLine("Move created .msi");
                        var msiPath = Path.Combine(wixProjectDirectory, @"bin\Release\Wasabi.msi");
                        if (!File.Exists(msiPath))
                        {
                            throw new Exception(".msi doesn't exist. Expected path: Wasabi.msi.");
                        }
                        var msiFileName = Path.GetFileNameWithoutExtension(msiPath);
                        var newMsiPath  = Path.Combine(binDistDirectory, $"{msiFileName}-{versionPrefix}.msi");
                        File.Move(msiPath, newMsiPath);

                        // Sign code with digicert.
                        var psiSigntool = new ProcessStartInfo
                        {
                            FileName = "cmd",
                            RedirectStandardInput = true,
                            WorkingDirectory      = binDistDirectory
                        };
                        using (var signToolProcess = Process.Start(psiSigntool))
                        {
                            signToolProcess.StandardInput.WriteLine($"signtool sign /d \"Wasabi Wallet\" /f \"{pfxPath}\" /p {pfxPassword} /t http://timestamp.digicert.com /a \"{newMsiPath}\" && exit");
                            signToolProcess.WaitForExit();
                        }

                        IoHelpers.DeleteRecursivelyWithMagicDustAsync(publishedFolder).GetAwaiter().GetResult();
                        Console.WriteLine($"Deleted {publishedFolder}");
                    }
                }

                Console.WriteLine("Signing final files...");
                var finalFiles = Directory.GetFiles(binDistDirectory);

                foreach (var finalFile in finalFiles)
                {
                    var psiSignProcess = new ProcessStartInfo
                    {
                        FileName = "cmd",
                        RedirectStandardInput = true,
                        WorkingDirectory      = binDistDirectory
                    };
                    using (var signProcess = Process.Start(psiSignProcess))
                    {
                        signProcess.StandardInput.WriteLine($"gpg --armor --detach-sign {finalFile} && exit");
                        signProcess.WaitForExit();
                    }

                    var psiRestoreHeat = new ProcessStartInfo
                    {
                        FileName = "cmd",
                        RedirectStandardInput = true,
                        WorkingDirectory      = wixProjectDirectory
                    };
                    using (var restoreHeatProcess = Process.Start(psiRestoreHeat))
                    {
                        restoreHeatProcess.StandardInput.WriteLine($"git checkout -- ComponentsGenerated.wxs && exit");
                        restoreHeatProcess.WaitForExit();
                    }

                    if (doRestoreThisFile)
                    {
                        var psiRestoreThisFile = new ProcessStartInfo
                        {
                            FileName = "cmd",
                            RedirectStandardInput = true,
                            WorkingDirectory      = packagerProjectDirectory
                        };
                        using (var restoreThisFileProcess = Process.Start(psiRestoreThisFile))
                        {
                            restoreThisFileProcess.StandardInput.WriteLine($"git checkout -- Program.cs && exit");
                            restoreThisFileProcess.WaitForExit();
                        }
                    }
                }

                IoHelpers.OpenFolderInFileExplorer(binDistDirectory);
                return;                 // No need for readkey here.
            }
        }