Skip to content

Commit

Permalink
Auto-Updater may fail for signed electron apps #116
Browse files Browse the repository at this point in the history
* add typescript script to update the checksum metadata for an installer
and also include the version number in the installer filename path
* change build process so that the windows installer gets a fixed
checksum after signing and create a link to the installer including the
version number
* also build zip for mac because required for updates
  • Loading branch information
jfaltermeier committed Sep 2, 2021
1 parent f7ac586 commit 05aba3e
Show file tree
Hide file tree
Showing 5 changed files with 290 additions and 6 deletions.
79 changes: 75 additions & 4 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -164,12 +164,66 @@ spec:
}
}
stage('Sign and Upload Windows') {
agent any
agent {
kubernetes {
yaml """
apiVersion: v1
kind: Pod
spec:
containers:
- name: theia-dev
image: eclipsetheia/theia-blueprint
command:
- cat
tty: true
resources:
limits:
memory: "8Gi"
cpu: "2"
requests:
memory: "8Gi"
cpu: "2"
volumeMounts:
- name: global-cache
mountPath: /.cache
- name: global-yarn
mountPath: /.yarn
- name: global-npm
mountPath: /.npm
- name: electron-cache
mountPath: /.electron-gyp
- name: jnlp
volumeMounts:
- name: volume-known-hosts
mountPath: /home/jenkins/.ssh
volumes:
- name: global-cache
emptyDir: {}
- name: global-yarn
emptyDir: {}
- name: global-npm
emptyDir: {}
- name: electron-cache
emptyDir: {}
- name: volume-known-hosts
configMap:
name: known-hosts
"""
}
}
steps {
unstash 'win'
script {
signInstaller('exe', 'winsign')
uploadInstaller('windows')
container('theia-dev') {
script {
signInstaller('exe', 'winsign')
updateMetadata('TheiaBlueprint.exe', 'latest.yml')
}
}
container('jnlp') {
script {
uploadInstaller('windows')
linkInstaller('windows', 'TheiaBlueprint', 'exe')
}
}
}
}
Expand Down Expand Up @@ -240,6 +294,11 @@ def notarizeInstaller(String ext) {
}
}

def updateMetadata(String executable, String yaml) {
sh "yarn install --force"
sh "yarn electron update:checksum -e ${executable} -y ${yaml}"
}

def uploadInstaller(String platform) {
if (env.BRANCH_NAME == releaseBranch) {
def packageJSON = readJSON file: "package.json"
Expand All @@ -256,3 +315,15 @@ def uploadInstaller(String platform) {
echo "Skipped upload for branch ${env.BRANCH_NAME}"
}
}

def linkInstaller(String platform, String installer, String extension) {
if (env.BRANCH_NAME == releaseBranch) {
def packageJSON = readJSON file: "package.json"
String version = "${packageJSON.version}"
sshagent(['projects-storage.eclipse.org-bot-ssh']) {
sh "ssh genie.theia@projects-storage.eclipse.org ln -s /home/data/httpd/download.eclipse.org/theia/latest/${platform}/${installer}.${extension} /home/data/httpd/download.eclipse.org/theia/latest/${platform}/${installer}-${version}.${extension}"
}
} else {
echo "Skipped copying installer for branch ${env.BRANCH_NAME}"
}
}
1 change: 1 addition & 0 deletions applications/electron/electron-builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ mac:
darkModeSupport: true
target:
- dmg
- zip
publish:
provider: generic
url: "https://download.eclipse.org/theia/latest/macos"
Expand Down
8 changes: 7 additions & 1 deletion applications/electron/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@
},
"devDependencies": {
"@theia/cli": "1.17.1",
"@types/js-yaml": "^3.12.0",
"@types/yargs": "^17.0.2",
"@wdio/cli": "^6.10.2",
"@wdio/local-runner": "^6.10.2",
"@wdio/mocha-framework": "^6.8.0",
Expand All @@ -89,10 +91,13 @@
"electron-builder": "^22.8.0",
"electron-chromedriver": "9.0.0",
"electron-mocha": "^9.3.2",
"js-yaml": "^3.12.0",
"mocha": "^8.2.1",
"rimraf": "^2.7.1",
"ts-node": "^10.0.0",
"wdio-chromedriver-service": "^6.0.4",
"webdriverio": "^6.10.2"
"webdriverio": "^6.10.2",
"yargs": "^17.0.1"
},
"scripts": {
"prepare": "yarn build && yarn download:plugins",
Expand All @@ -106,6 +111,7 @@
"package": "yarn clean:dist && electron-builder -c.mac.identity=null --publish never",
"deploy": "yarn clean:dist && electron-builder -c.mac.identity=null --publish always",
"package:preview": "yarn clean:dist && electron-builder --dir",
"update:checksum": "ts-node scripts/update-checksum.ts",
"download:plugins": "theia download:plugins",
"test": "mocha --timeout 60000 \"./test/*.spec.js\""
},
Expand Down
106 changes: 106 additions & 0 deletions applications/electron/scripts/update-checksum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/********************************************************************************
* Copyright (C) 2021 EclipseSource and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import * as crypto from 'crypto';
import * as fs from 'fs';
import * as jsyaml from 'js-yaml';
import * as path from 'path';
import { hideBin } from 'yargs/helpers';
import yargs from 'yargs/yargs';


const argv = yargs(hideBin(process.argv))
.option('executable', { alias: 'e', type: 'string', default: 'TheiaBlueprint.AppImage', desription: 'The executable for which the checksum needs to be updated' })
.option('yaml', { alias: 'y', type: 'string', default: 'latest-linux.yml', desription: 'The yaml file where the checksum needs to be updated' })
.version(false)
.wrap(120)
.parseSync();

execute();

async function execute(): Promise<void> {
const executable = argv.executable;
const yaml = argv.yaml;

const executablePath = path.resolve(
__dirname,
'../dist/',
executable
);

const yamlPath = path.resolve(
__dirname,
'../dist/',
yaml
);

console.log('Exe: ' + executablePath + '; Yaml: ' + yamlPath)

const hash = await hashFile(executablePath, 'sha512', 'base64', {});
const size = fs.statSync(executablePath).size

const yamlContents: string = fs.readFileSync(yamlPath, { encoding: 'utf8' })
const latestYaml: any = jsyaml.safeLoad(yamlContents);
latestYaml.sha512 = hash;
latestYaml.path = updatedPath(latestYaml.path, latestYaml.version)
for (const file of latestYaml.files) {
file.sha512 = hash;
file.size = size;
file.url = updatedPath(file.url, latestYaml.version)
}

//line width -1 to avoid adding >- on long strings like a hash
const newYamlContents = jsyaml.dump(latestYaml, { lineWidth: -1 });
fs.writeFileSync(yamlPath, newYamlContents);
}

function hashFile(file: fs.PathLike, algorithm = 'sha512', encoding: BufferEncoding = 'base64', options: string | {
flags?: string;
encoding?: BufferEncoding;
fd?: number;
mode?: number;
autoClose?: boolean;
emitClose?: boolean;
start?: number;
end?: number;
highWaterMark?: number;
}): Promise<any> {
return new Promise((resolve, reject) => {
const hash = crypto.createHash(algorithm);
hash.on('error', reject).setEncoding(encoding);
fs.createReadStream(
file,
Object.assign({}, options, {
highWaterMark: 1024 * 1024,
})
)
.on('error', reject)
.on('end', () => {
hash.end();
resolve(hash.read());
})
.pipe(
hash,
{
end: false,
}
);
});
}

function updatedPath(path: string, version: string): string {
const extensionIndex = path.lastIndexOf('.');
return path.substring(0, extensionIndex) + '-' + version + path.substring(extensionIndex);
}

0 comments on commit 05aba3e

Please sign in to comment.