mirror of
https://github.com/bitwarden/browser
synced 2026-01-21 11:53:34 +00:00
Enable cross-compilation and packaging of Windows Appx from macOS (#17976)
* Enable cross-compilation and packaging of Windows Appx from macOS * Consolidate cargo build execution into a single function in native build script * Install cargo-xwin when needed * Install Appx tools when needed * Consolidate command execution into a single function in native build script * Only include the native node modules for the appropriate platform electron-builder's globs interact strangely, so we can't exclude all the .node files in the global config and then include the platform-specific files in the platform configuration. * Always copy Rust binaries to dist folder * Log source and destination when copying files * Update copyright * Match Electron version in Beta build
This commit is contained in:
111
apps/desktop/custom-appx-manifest.xml
Normal file
111
apps/desktop/custom-appx-manifest.xml
Normal file
@@ -0,0 +1,111 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--suppress XmlUnusedNamespaceDeclaration -->
|
||||
<!-- <Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"> -->
|
||||
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
|
||||
xmlns:uap10="http://schemas.microsoft.com/appx/manifest/uap/windows10/10"
|
||||
IgnorableNamespaces="uap rescap com uap10 build"
|
||||
xmlns:build="http://schemas.microsoft.com/developer/appx/2015/build">
|
||||
<!-- use single quotes to avoid double quotes escaping in the publisher value -->
|
||||
<Identity Name="${applicationId}"
|
||||
ProcessorArchitecture="${arch}"
|
||||
Publisher='${publisher}'
|
||||
Version="${version}" />
|
||||
<Properties>
|
||||
<DisplayName>${displayName}</DisplayName>
|
||||
<PublisherDisplayName>${publisherDisplayName}</PublisherDisplayName>
|
||||
<Description>A secure and free password manager for all of your devices.</Description>
|
||||
<Logo>assets\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
<Resources>
|
||||
<Resource Language="en-US" />
|
||||
<Resource Language="af" />
|
||||
<Resource Language="ar" />
|
||||
<Resource Language="az-latn" />
|
||||
<Resource Language="be" />
|
||||
<Resource Language="bg" />
|
||||
<Resource Language="bn" />
|
||||
<Resource Language="bs" />
|
||||
<Resource Language="ca" />
|
||||
<Resource Language="cs" />
|
||||
<Resource Language="cy" />
|
||||
<Resource Language="da" />
|
||||
<Resource Language="de" />
|
||||
<Resource Language="el" />
|
||||
<Resource Language="en-gb" />
|
||||
<Resource Language="en-in" />
|
||||
<Resource Language="es" />
|
||||
<Resource Language="et" />
|
||||
<Resource Language="eu" />
|
||||
<Resource Language="fa" />
|
||||
<Resource Language="fi" />
|
||||
<Resource Language="fil" />
|
||||
<Resource Language="fr" />
|
||||
<Resource Language="gl" />
|
||||
<Resource Language="he" />
|
||||
<Resource Language="hi" />
|
||||
<Resource Language="hr" />
|
||||
<Resource Language="hu" />
|
||||
<Resource Language="id" />
|
||||
<Resource Language="it" />
|
||||
<Resource Language="ja" />
|
||||
<Resource Language="ka" />
|
||||
<Resource Language="km" />
|
||||
<Resource Language="kn" />
|
||||
<Resource Language="ko" />
|
||||
<Resource Language="lt" />
|
||||
<Resource Language="lv" />
|
||||
<Resource Language="ml" />
|
||||
<Resource Language="mr" />
|
||||
<Resource Language="nb" />
|
||||
<Resource Language="ne" />
|
||||
<Resource Language="nl" />
|
||||
<Resource Language="nn" />
|
||||
<Resource Language="or" />
|
||||
<Resource Language="pl" />
|
||||
<Resource Language="pt-br" />
|
||||
<Resource Language="pt-pt" />
|
||||
<Resource Language="ro" />
|
||||
<Resource Language="ru" />
|
||||
<Resource Language="si" />
|
||||
<Resource Language="sk" />
|
||||
<Resource Language="sl" />
|
||||
<Resource Language="sr-cyrl" />
|
||||
<Resource Language="sv" />
|
||||
<Resource Language="te" />
|
||||
<Resource Language="th" />
|
||||
<Resource Language="tr" />
|
||||
<Resource Language="uk" />
|
||||
<Resource Language="vi" />
|
||||
<Resource Language="zh-cn" />
|
||||
<Resource Language="zh-tw" />
|
||||
</Resources>
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.26200.7019"
|
||||
MaxVersionTested="10.0.26200.7171" />
|
||||
</Dependencies>
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
<Applications>
|
||||
<Application Id="bitwardendesktop" Executable="${executable}"
|
||||
EntryPoint="Windows.FullTrustApplication">
|
||||
<uap:VisualElements
|
||||
BackgroundColor="#175DDC"
|
||||
DisplayName="${displayName}"
|
||||
Square150x150Logo="assets\Square150x150Logo.png"
|
||||
Square44x44Logo="assets\Square44x44Logo.png"
|
||||
Description="A secure and free password manager for all of your devices.">
|
||||
<uap:LockScreen Notification="badgeAndTileText" BadgeLogo="assets\BadgeLogo.png" />
|
||||
<uap:DefaultTile Wide310x150Logo="assets\Wide310x150Logo.png" />
|
||||
<uap:SplashScreen Image="assets\SplashScreen.png" />
|
||||
</uap:VisualElements>
|
||||
</Application>
|
||||
</Applications>
|
||||
</Package>
|
||||
@@ -20,47 +20,79 @@ fs.mkdirSync(path.join(__dirname, "dist"), { recursive: true });
|
||||
|
||||
const args = process.argv.slice(2); // Get arguments passed to the script
|
||||
const mode = args.includes("--release") ? "release" : "debug";
|
||||
const isRelease = mode === "release";
|
||||
const targetArg = args.find(arg => arg.startsWith("--target="));
|
||||
const target = targetArg ? targetArg.split("=")[1] : null;
|
||||
|
||||
let crossPlatform = process.argv.length > 2 && process.argv[2] === "cross-platform";
|
||||
|
||||
/**
|
||||
* Execute a command.
|
||||
* @param {string} bin Executable to run.
|
||||
* @param {string[]} args Arguments for executable.
|
||||
* @param {string} [workingDirectory] Path to working directory, relative to the script directory. Defaults to the script directory.
|
||||
* @param {string} [useShell] Whether to use a shell to execute the command. Defaults to false.
|
||||
*/
|
||||
function runCommand(bin, args, workingDirectory = "", useShell = false) {
|
||||
const options = { stdio: 'inherit', cwd: path.resolve(__dirname, workingDirectory), shell: useShell }
|
||||
console.debug("Running command:", bin, args, options)
|
||||
child_process.execFileSync(bin, args, options)
|
||||
}
|
||||
|
||||
function buildNapiModule(target, release = true) {
|
||||
const targetArg = target ? `--target ${target}` : "";
|
||||
const targetArg = target ? `--target=${target}` : "";
|
||||
const releaseArg = release ? "--release" : "";
|
||||
child_process.execSync(`npm run build -- ${releaseArg} ${targetArg}`, { stdio: 'inherit', cwd: path.join(__dirname, "napi") });
|
||||
const crossCompileArg = effectivePlatform(target) !== process.platform ? "--cross-compile" : "";
|
||||
runCommand("npm", ["run", "build", "--", crossCompileArg, releaseArg, targetArg].filter(s => s != ''), "./napi", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a Rust binary with Cargo.
|
||||
*
|
||||
* If {@link target} is specified, cross-compilation helpers are used to build if necessary, and the resulting
|
||||
* binary is copied to the `dist` folder.
|
||||
* @param {string} bin Name of cargo binary package in `desktop_native` workspace.
|
||||
* @param {string?} target Rust compiler target, e.g. `aarch64-pc-windows-msvc`.
|
||||
* @param {boolean} release Whether to build in release mode.
|
||||
*/
|
||||
function cargoBuild(bin, target, release) {
|
||||
const targetArg = target ? `--target=${target}` : "";
|
||||
const releaseArg = release ? "--release" : "";
|
||||
const args = ["build", "--bin", bin, releaseArg, targetArg]
|
||||
// Use cross-compilation helper if necessary
|
||||
if (effectivePlatform(target) === "win32" && process.platform !== "win32") {
|
||||
args.unshift("xwin")
|
||||
}
|
||||
runCommand("cargo", args.filter(s => s != ''))
|
||||
|
||||
// Infer the architecture and platform if not passed explicitly
|
||||
let nodeArch, platform;
|
||||
if (target) {
|
||||
nodeArch = rustTargetsMap[target].nodeArch;
|
||||
platform = rustTargetsMap[target].platform;
|
||||
}
|
||||
else {
|
||||
nodeArch = process.arch;
|
||||
platform = process.platform;
|
||||
}
|
||||
|
||||
// Copy the resulting binary to the dist folder
|
||||
const profileFolder = isRelease ? "release" : "debug";
|
||||
const ext = platform === "win32" ? ".exe" : "";
|
||||
const src = path.join(__dirname, "target", target ? target : "", profileFolder, `${bin}${ext}`)
|
||||
const dst = path.join(__dirname, "dist", `${bin}.${platform}-${nodeArch}${ext}`)
|
||||
console.log(`Copying ${src} to ${dst}`);
|
||||
fs.copyFileSync(src, dst);
|
||||
}
|
||||
|
||||
function buildProxyBin(target, release = true) {
|
||||
const targetArg = target ? `--target ${target}` : "";
|
||||
const releaseArg = release ? "--release" : "";
|
||||
child_process.execSync(`cargo build --bin desktop_proxy ${releaseArg} ${targetArg}`, {stdio: 'inherit', cwd: path.join(__dirname, "proxy")});
|
||||
|
||||
if (target) {
|
||||
// Copy the resulting binary to the dist folder
|
||||
const targetFolder = release ? "release" : "debug";
|
||||
const ext = process.platform === "win32" ? ".exe" : "";
|
||||
const nodeArch = rustTargetsMap[target].nodeArch;
|
||||
fs.copyFileSync(path.join(__dirname, "target", target, targetFolder, `desktop_proxy${ext}`), path.join(__dirname, "dist", `desktop_proxy.${process.platform}-${nodeArch}${ext}`));
|
||||
}
|
||||
cargoBuild("desktop_proxy", target, release)
|
||||
}
|
||||
|
||||
function buildImporterBinaries(target, release = true) {
|
||||
// These binaries are only built for Windows, so we can skip them on other platforms
|
||||
if (process.platform !== "win32") {
|
||||
return;
|
||||
}
|
||||
|
||||
const bin = "bitwarden_chromium_import_helper";
|
||||
const targetArg = target ? `--target ${target}` : "";
|
||||
const releaseArg = release ? "--release" : "";
|
||||
child_process.execSync(`cargo build --bin ${bin} ${releaseArg} ${targetArg}`);
|
||||
|
||||
if (target) {
|
||||
// Copy the resulting binary to the dist folder
|
||||
const targetFolder = release ? "release" : "debug";
|
||||
const nodeArch = rustTargetsMap[target].nodeArch;
|
||||
fs.copyFileSync(path.join(__dirname, "target", target, targetFolder, `${bin}.exe`), path.join(__dirname, "dist", `${bin}.${process.platform}-${nodeArch}.exe`));
|
||||
if (effectivePlatform(target) == "win32") {
|
||||
cargoBuild("bitwarden_chromium_import_helper", target, release)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,17 +101,29 @@ function buildProcessIsolation() {
|
||||
return;
|
||||
}
|
||||
|
||||
child_process.execSync(`cargo build --release`, {
|
||||
stdio: 'inherit',
|
||||
cwd: path.join(__dirname, "process_isolation")
|
||||
});
|
||||
runCommand("cargo", ["build", "--package", "process_isolation", "--release"]);
|
||||
|
||||
console.log("Copying process isolation library to dist folder");
|
||||
fs.copyFileSync(path.join(__dirname, "target", "release", "libprocess_isolation.so"), path.join(__dirname, "dist", `libprocess_isolation.so`));
|
||||
}
|
||||
|
||||
function installTarget(target) {
|
||||
child_process.execSync(`rustup target add ${target}`, { stdio: 'inherit', cwd: __dirname });
|
||||
runCommand("rustup", ["target", "add", target]);
|
||||
// Install cargo-xwin for cross-platform builds targeting Windows
|
||||
if (target.includes('windows') && process.platform !== 'win32') {
|
||||
runCommand("cargo", ["install", "--version", "0.20.2", "--locked", "cargo-xwin"]);
|
||||
// install tools needed for packaging Appx, only supported on macOS for now.
|
||||
if (process.platform === "darwin") {
|
||||
runCommand("brew", ["install", "iinuwa/msix-packaging-tap/msix-packaging", "osslsigncode"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function effectivePlatform(target) {
|
||||
if (target) {
|
||||
return rustTargetsMap[target].platform
|
||||
}
|
||||
return process.platform
|
||||
}
|
||||
|
||||
if (!crossPlatform && !target) {
|
||||
@@ -94,9 +138,9 @@ if (!crossPlatform && !target) {
|
||||
if (target) {
|
||||
console.log(`Building for target: ${target} in ${mode} mode`);
|
||||
installTarget(target);
|
||||
buildNapiModule(target, mode === "release");
|
||||
buildProxyBin(target, mode === "release");
|
||||
buildImporterBinaries(false, mode === "release");
|
||||
buildNapiModule(target, isRelease);
|
||||
buildProxyBin(target, isRelease);
|
||||
buildImporterBinaries(target, isRelease);
|
||||
buildProcessIsolation();
|
||||
return;
|
||||
}
|
||||
@@ -113,8 +157,8 @@ if (process.platform === "linux") {
|
||||
|
||||
platformTargets.forEach(([target, _]) => {
|
||||
installTarget(target);
|
||||
buildNapiModule(target, mode === "release");
|
||||
buildProxyBin(target, mode === "release");
|
||||
buildImporterBinaries(target, mode === "release");
|
||||
buildNapiModule(target, isRelease);
|
||||
buildProxyBin(target, isRelease);
|
||||
buildImporterBinaries(target, isRelease);
|
||||
buildProcessIsolation();
|
||||
});
|
||||
|
||||
@@ -13,14 +13,15 @@
|
||||
},
|
||||
"afterSign": "scripts/after-sign.js",
|
||||
"afterPack": "scripts/after-pack.js",
|
||||
"asarUnpack": ["**/*.node"],
|
||||
"beforePack": "scripts/before-pack.js",
|
||||
"files": [
|
||||
"**/*",
|
||||
"!**/node_modules/@bitwarden/desktop-napi/**/*",
|
||||
"**/node_modules/@bitwarden/desktop-napi/index.js",
|
||||
"**/node_modules/@bitwarden/desktop-napi/desktop_napi.${platform}-${arch}*.node"
|
||||
"!node_modules/@bitwarden/desktop-napi/scripts",
|
||||
"!node_modules/@bitwarden/desktop-napi/src",
|
||||
"!node_modules/@bitwarden/desktop-napi/Cargo.toml",
|
||||
"!node_modules/@bitwarden/desktop-napi/build.rs",
|
||||
"!node_modules/@bitwarden/desktop-napi/package.json"
|
||||
],
|
||||
"electronVersion": "36.8.1",
|
||||
"electronVersion": "37.7.0",
|
||||
"generateUpdatesFilesForAllChannels": true,
|
||||
"publish": {
|
||||
"provider": "generic",
|
||||
@@ -34,11 +35,11 @@
|
||||
},
|
||||
"extraFiles": [
|
||||
{
|
||||
"from": "desktop_native/dist/desktop_proxy.${platform}-${arch}.exe",
|
||||
"from": "desktop_native/dist/desktop_proxy.win32-${arch}.exe",
|
||||
"to": "desktop_proxy.exe"
|
||||
},
|
||||
{
|
||||
"from": "desktop_native/dist/bitwarden_chromium_import_helper.${platform}-${arch}.exe",
|
||||
"from": "desktop_native/dist/bitwarden_chromium_import_helper.win32-${arch}.exe",
|
||||
"to": "bitwarden_chromium_import_helper.exe"
|
||||
}
|
||||
]
|
||||
@@ -58,6 +59,7 @@
|
||||
"appx": {
|
||||
"artifactName": "Bitwarden-Beta-${version}-${arch}.${ext}",
|
||||
"backgroundColor": "#175DDC",
|
||||
"customManifestPath": "./custom-appx-manifest.xml",
|
||||
"applicationId": "BitwardenBeta",
|
||||
"identityName": "8bitSolutionsLLC.BitwardenBeta",
|
||||
"publisher": "CN=14D52771-DE3C-4886-B8BF-825BA7690418",
|
||||
|
||||
@@ -13,12 +13,13 @@
|
||||
},
|
||||
"afterSign": "scripts/after-sign.js",
|
||||
"afterPack": "scripts/after-pack.js",
|
||||
"asarUnpack": ["**/*.node"],
|
||||
"beforePack": "scripts/before-pack.js",
|
||||
"files": [
|
||||
"**/*",
|
||||
"!**/node_modules/@bitwarden/desktop-napi/**/*",
|
||||
"**/node_modules/@bitwarden/desktop-napi/index.js",
|
||||
"**/node_modules/@bitwarden/desktop-napi/desktop_napi.${platform}-${arch}*.node"
|
||||
"!node_modules/@bitwarden/desktop-napi/scripts",
|
||||
"!node_modules/@bitwarden/desktop-napi/src",
|
||||
"!node_modules/@bitwarden/desktop-napi/Cargo.toml",
|
||||
"!node_modules/@bitwarden/desktop-napi/build.rs",
|
||||
"!node_modules/@bitwarden/desktop-napi/package.json"
|
||||
],
|
||||
"electronVersion": "39.2.6",
|
||||
"generateUpdatesFilesForAllChannels": true,
|
||||
@@ -94,11 +95,11 @@
|
||||
},
|
||||
"extraFiles": [
|
||||
{
|
||||
"from": "desktop_native/dist/desktop_proxy.${platform}-${arch}.exe",
|
||||
"from": "desktop_native/dist/desktop_proxy.win32-${arch}.exe",
|
||||
"to": "desktop_proxy.exe"
|
||||
},
|
||||
{
|
||||
"from": "desktop_native/dist/bitwarden_chromium_import_helper.${platform}-${arch}.exe",
|
||||
"from": "desktop_native/dist/bitwarden_chromium_import_helper.win32-${arch}.exe",
|
||||
"to": "bitwarden_chromium_import_helper.exe"
|
||||
}
|
||||
]
|
||||
@@ -172,6 +173,7 @@
|
||||
"appx": {
|
||||
"artifactName": "${productName}-${version}-${arch}.${ext}",
|
||||
"backgroundColor": "#175DDC",
|
||||
"customManifestPath": "./custom-appx-manifest.xml",
|
||||
"applicationId": "bitwardendesktop",
|
||||
"identityName": "8bitSolutionsLLC.bitwardendesktop",
|
||||
"publisher": "CN=14D52771-DE3C-4886-B8BF-825BA7690418",
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"build:macos-extension:mas": "./desktop_native/macos_provider/build.sh && node scripts/build-macos-extension.js mas",
|
||||
"build:macos-extension:masdev": "./desktop_native/macos_provider/build.sh && node scripts/build-macos-extension.js mas-dev",
|
||||
"build:main": "cross-env NODE_ENV=production webpack --config webpack.config.js --config-name main",
|
||||
"build:main:dev": "npm run build-native && cross-env NODE_ENV=development webpack --config webpack.config.js --config-name main",
|
||||
"build:main:dev": "cross-env NODE_ENV=development webpack --config webpack.config.js --config-name main",
|
||||
"build:main:watch": "npm run build-native && cross-env NODE_ENV=development webpack --config webpack.config.js --config-name main --watch",
|
||||
"build:renderer": "cross-env NODE_ENV=production webpack --config webpack.config.js --config-name renderer",
|
||||
"build:renderer:dev": "cross-env NODE_ENV=development webpack --config webpack.config.js --config-name renderer",
|
||||
|
||||
@@ -6,9 +6,12 @@ const path = require("path");
|
||||
const { flipFuses, FuseVersion, FuseV1Options } = require("@electron/fuses");
|
||||
const builder = require("electron-builder");
|
||||
const fse = require("fs-extra");
|
||||
|
||||
exports.default = run;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {builder.AfterPackContext} context
|
||||
*/
|
||||
async function run(context) {
|
||||
console.log("## After pack");
|
||||
// console.log(context);
|
||||
|
||||
226
apps/desktop/scripts/appx-cross-build.ps1
Executable file
226
apps/desktop/scripts/appx-cross-build.ps1
Executable file
@@ -0,0 +1,226 @@
|
||||
#!/usr/bin/env pwsh
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Script to build, package and sign the Bitwarden desktop client as a Windows Appx
|
||||
package.
|
||||
|
||||
.DESCRIPTION
|
||||
This script provides cross-platform support for packaging and signing the
|
||||
Bitwarden desktop client as a Windows Appx package.
|
||||
|
||||
Currently, only macOS -> Windows Appx is supported, but Linux -> Windows Appx
|
||||
could be added in the future by providing Linux binaries for the msix-packaging
|
||||
project.
|
||||
|
||||
.NOTES
|
||||
The reason this script exists is because electron-builder does not currently
|
||||
support cross-platform Appx packaging without proprietary tools (Parallels
|
||||
Windows VM). This script uses third-party tools (makemsix from msix-packaging
|
||||
and osslsigncode) to package and sign the Appx.
|
||||
|
||||
The signing certificate must have the same subject as the publisher name. This
|
||||
can be generated on the Windows target using PowerShell 5.1 and copied to the
|
||||
host, or directly on the host with OpenSSL.
|
||||
|
||||
Using Windows PowerShell 5.1:
|
||||
```powershell
|
||||
$publisher = "CN=Bitwarden Inc., O=Bitwarden Inc., L=Santa Barbara, S=California, C=US, SERIALNUMBER=7654941, OID.2.5.4.15=Private Organization, OID.1.3.6.1.4.1.311.60.2.1.2=Delaware, OID.1.3.6.1.4.1.311.60.2.1.3=US"
|
||||
$certificate = New-SelfSignedCertificate -Type Custom -KeyUsage DigitalSignature -CertStoreLocation "Cert:\CurrentUser\My" -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.3", "2.5.29.19={text}") -Subject $publisher -FriendlyName "Bitwarden Developer Signing Certificate"
|
||||
$password = Read-Host -AsSecureString
|
||||
Export-PfxCertificate -cert "Cert:\CurrentUser\My\${$certificate.Thumbprint}" -FilePath "C:\path/to/pfx" -Password $password
|
||||
```
|
||||
|
||||
Using OpenSSL:
|
||||
```sh
|
||||
subject="jurisdictionCountryName=US/jurisdictionStateOrProvinceName=Delaware/businessCategory=Private Organization/serialNumber=7654941, C=US, ST=California, L=Santa Barbara, O=Bitwarden Inc., CN=Bitwarden Inc."
|
||||
keyfile="/tmp/mysigning.rsa.pem"
|
||||
certfile="/tmp/mysigning.cert.pem"
|
||||
p12file="/tmp/mysigning.p12"
|
||||
openssl req -x509 -keyout "$keyfile" -out "$certfile" -subj "$subject" \
|
||||
-newkey rsa:2048 -days 3650 -nodes \
|
||||
-addext 'keyUsage=critical,digitalSignature' \
|
||||
-addext 'extendedKeyUsage=critical,codeSigning' \
|
||||
-addext 'basicConstraints=critical,CA:FALSE'
|
||||
openssl pkcs12 -inkey "$keyfile" -in "$certfile" -export -out "$p12file"
|
||||
rm $keyfile
|
||||
```
|
||||
|
||||
.EXAMPLE
|
||||
./scripts/cross-build.ps1 -Architecture arm64 -CertificatePath ~/Development/code-signing.pfx -CertificatePassword (Read-Host -AsSecureString) -Release -Beta
|
||||
|
||||
Reads the signing certificate password from user input, then builds, packages
|
||||
and signs the Appx.
|
||||
|
||||
Alternatively, you can specify the CERTIFICATE_PASSWORD environment variable.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[ValidateSet("X64", "ARM64")]$Architecture,
|
||||
[string]
|
||||
# Path to PKCS12 certificate file. If not specified, the Appx will not be signed.
|
||||
$CertificatePath,
|
||||
[SecureString]
|
||||
# Password for PKCS12 certificate. Alternatively, may be specified in
|
||||
# CERTIFICATE_PASSWORD environment variable. If not specified, the Appx will
|
||||
# not be signed.
|
||||
$CertificatePassword,
|
||||
[Switch]
|
||||
# Whether to build the Beta version of the app.
|
||||
$Beta=$false,
|
||||
[Switch]
|
||||
# Whether to build in release mode.
|
||||
$Release=$false
|
||||
)
|
||||
$ErrorActionPreference = "Stop"
|
||||
$PSNativeCommandUseErrorActionPreference = $true
|
||||
$startTime = Get-Date
|
||||
$originalLocation = Get-Location
|
||||
if (!(Get-Command makemsix -ErrorAction SilentlyContinue)) {
|
||||
Write-Error "The `makemsix` tool from the msix-packaging project is required to construct Appx package."
|
||||
Write-Error "On macOS, you can install with Homebrew:"
|
||||
Write-Error " brew install iinuwa/msix-packaging-tap/msix-packaging"
|
||||
Exit 1
|
||||
}
|
||||
|
||||
if (!(Get-Command osslsigncode -ErrorAction SilentlyContinue)) {
|
||||
Write-Error "The `osslsigncode` tool is required to sign the Appx package."
|
||||
Write-Error "On macOS, you can install with Homebrew:"
|
||||
Write-Error " brew install osslsigncode"
|
||||
Exit 1
|
||||
}
|
||||
|
||||
if (!(Get-Command cargo-xwin -ErrorAction SilentlyContinue)) {
|
||||
Write-Error "The `cargo-xwin` tool is required to cross-compile Windows native code."
|
||||
Write-Error "You can install with cargo:"
|
||||
Write-Error " cargo install --version 0.20.2 --locked cargo-xwin"
|
||||
Exit 1
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
# Resolve certificate file before we change directories.
|
||||
$CertificateFile = Get-Item $CertificatePath -ErrorAction SilentlyContinue
|
||||
|
||||
cd $PSScriptRoot/..
|
||||
|
||||
if ($Beta) {
|
||||
$electronConfigFile = Get-Item "./electron-builder.beta.json"
|
||||
}
|
||||
else {
|
||||
$electronConfigFile = Get-Item "./electron-builder.json"
|
||||
}
|
||||
|
||||
$builderConfig = Get-Content $electronConfigFile | ConvertFrom-Json
|
||||
$packageConfig = Get-Content package.json | ConvertFrom-Json
|
||||
$manifestTemplate = Get-Content $builderConfig.appx.customManifestPath
|
||||
|
||||
$srcDir = Get-Location
|
||||
$assetsDir = Get-Item $builderConfig.directories.buildResources
|
||||
$buildDir = Get-Item $builderConfig.directories.app
|
||||
$outDir = Join-Path (Get-Location) ($builderConfig.directories.output ?? "dist")
|
||||
|
||||
if ($Release) {
|
||||
$buildConfiguration = "--release"
|
||||
}
|
||||
$arch = "$Architecture".ToLower()
|
||||
$ext = "appx"
|
||||
$version = Get-Date -Format "yyyy.M.d.1HHmm"
|
||||
$productName = $builderConfig.productName
|
||||
$artifactName = "${productName}-$($packageConfig.version)-${arch}.$ext"
|
||||
|
||||
Write-Host "Building native code"
|
||||
$rustTarget = switch ($arch) {
|
||||
x64 { "x86_64-pc-windows-msvc" }
|
||||
arm64 { "aarch64-pc-windows-msvc" }
|
||||
default {
|
||||
Write-Error "Unsupported architecture: $Architecture. Supported architectures are x64 and arm64"
|
||||
Exit(1)
|
||||
}
|
||||
}
|
||||
npm run build-native -- cross-platform $buildConfiguration "--target=$rustTarget"
|
||||
|
||||
Write-Host "Building Javascript code"
|
||||
if ($Release) {
|
||||
npm run build
|
||||
}
|
||||
else {
|
||||
npm run build:dev
|
||||
}
|
||||
|
||||
Write-Host "Cleaning output folder"
|
||||
Remove-Item -Recurse -Force $outDir -ErrorAction Ignore
|
||||
|
||||
Write-Host "Packaging Electron executable"
|
||||
& npx electron-builder --config $electronConfigFile --publish never --dir --win --$arch
|
||||
|
||||
cd $outDir
|
||||
New-Item -Type Directory (Join-Path $outDir "appx")
|
||||
|
||||
Write-Host "Building Appx directory structure"
|
||||
$appxDir = (Join-Path $outDir appx/app)
|
||||
if ($arch -eq "x64") {
|
||||
Move-Item (Join-Path $outDir "win-unpacked") $appxDir
|
||||
}
|
||||
else {
|
||||
Move-Item (Join-Path $outDir "win-${arch}-unpacked") $appxDir
|
||||
}
|
||||
|
||||
Write-Host "Copying Assets"
|
||||
New-Item -Type Directory (Join-Path $outDir appx/assets)
|
||||
Copy-Item $srcDir/resources/appx/* $outDir/appx/assets/
|
||||
|
||||
Write-Host "Building Appx manifest"
|
||||
$translationMap = @{
|
||||
'arch' = $arch
|
||||
'applicationId' = $builderConfig.appx.applicationId
|
||||
'displayName' = $productName
|
||||
'executable' = "app\${productName}.exe"
|
||||
'publisher' = $builderConfig.appx.publisher
|
||||
'publisherDisplayName' = $builderConfig.appx.publisherDisplayName
|
||||
'version' = $version
|
||||
}
|
||||
|
||||
$manifest = $manifestTemplate
|
||||
$translationMap.Keys | ForEach-Object {
|
||||
$manifest = $manifest.Replace("`${$_}", $translationMap[$_])
|
||||
}
|
||||
$manifest | Out-File appx/AppxManifest.xml
|
||||
$unsignedArtifactpath = [System.IO.Path]::GetFileNameWithoutExtension($artifactName) + "-unsigned.$ext"
|
||||
Write-Host "Creating unsigned Appx"
|
||||
makemsix pack -d appx -p $unsignedArtifactpath
|
||||
|
||||
$outfile = Join-Path $outDir $unsignedArtifactPath
|
||||
if ($null -eq $CertificatePath) {
|
||||
Write-Warning "No Certificate specified. Not signing Appx."
|
||||
}
|
||||
elseif ($null -eq $CertificatePassword -and $null -eq $env:CERTIFICATE_PASSWORD) {
|
||||
Write-Warning "No certificate password specified in CertificatePassword argument nor CERTIFICATE_PASSWORD environment variable. Not signing Appx."
|
||||
}
|
||||
else {
|
||||
$cert = $CertificateFile
|
||||
$pw = $null
|
||||
if ($null -ne $CertificatePassword) {
|
||||
$pw = ConvertFrom-SecureString -SecureString $CertificatePassword -AsPlainText
|
||||
} else {
|
||||
$pw = $env:CERTIFICATE_PASSWORD
|
||||
}
|
||||
$unsigned = $outfile
|
||||
$outfile = (Join-Path $outDir $artifactName)
|
||||
Write-Host "Signing $artifactName with $cert"
|
||||
osslsigncode sign `
|
||||
-pkcs12 "$cert" `
|
||||
-pass "$pw" `
|
||||
-in $unsigned `
|
||||
-out $outfile
|
||||
Remove-Item $unsigned
|
||||
}
|
||||
|
||||
$endTime = Get-Date
|
||||
$elapsed = $endTime - $startTime
|
||||
Write-Host "Successfully packaged $(Get-Item $outfile)"
|
||||
Write-Host ("Finished at $($endTime.ToString('HH:mm:ss')) in $($elapsed.ToString('mm')) minutes and $($elapsed.ToString('ss')).$($elapsed.ToString('fff')) seconds")
|
||||
}
|
||||
finally {
|
||||
Set-Location -Path $originalLocation
|
||||
}
|
||||
31
apps/desktop/scripts/before-pack.js
Normal file
31
apps/desktop/scripts/before-pack.js
Normal file
@@ -0,0 +1,31 @@
|
||||
/* eslint-disable no-console */
|
||||
/** @import { BeforePackContext } from 'app-builder-lib' */
|
||||
exports.default = run;
|
||||
|
||||
/**
|
||||
* @param {BeforePackContext} context
|
||||
*/
|
||||
async function run(context) {
|
||||
console.log("## before pack");
|
||||
console.log("Stripping .node files that don't belong to this platform...");
|
||||
removeExtraNodeFiles(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes Node files for platforms besides the current platform being packaged.
|
||||
*
|
||||
* @param {BeforePackContext} context
|
||||
*/
|
||||
function removeExtraNodeFiles(context) {
|
||||
// When doing cross-platform builds, due to electron-builder limitiations,
|
||||
// .node files for other platforms may be generated and unpacked, so we
|
||||
// remove them manually here before signing and distributing.
|
||||
const packagerPlatform = context.packager.platform.nodeName;
|
||||
const platforms = ["darwin", "linux", "win32"];
|
||||
const fileFilter = context.packager.info._configuration.files[0].filter;
|
||||
for (const platform of platforms) {
|
||||
if (platform != packagerPlatform) {
|
||||
fileFilter.push(`!node_modules/@bitwarden/desktop-napi/desktop_napi.${platform}-*.node`);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user