mirror of
https://github.com/bitwarden/browser
synced 2026-01-31 08:43:54 +00:00
* 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
227 lines
8.1 KiB
PowerShell
Executable File
227 lines
8.1 KiB
PowerShell
Executable File
#!/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
|
|
}
|