Beta versions support: SxS support, in-app env/instance display (#1606)

* Script for beta config; unique data dir, in-app env/type display

To release a beta build, increment the version and add -beta-N to the
end, then go through all the standard release activities.

The prepare-build npm script then updates key bits of the package.json
to ensure that the beta build can be installed alongside a production
build. This includes a new name ('Signal Beta') and a different location
for application data.

Note: Beta builds can be installed alongside production builds.

As part of this, a couple new bits of data are shown across the app:

- Environment (development or test, not shown if production)
- App Instance (disabled in production; used for multiple accounts)

These are shown in:

- The window title - both environment and app instance. You can tell
  beta builds because the app name, preceding these data bits, is
  different.
- The about window - both environment and app instance. You can tell
  beta builds from the version number.
- The header added to the debug log - just environment. The version
  number will tell us if it's a beta build, and app instance isn't
  helpful.

* Turn on single-window mode in non-production modes

Because it's really frightening when you see 'unable to read from db'
errors in the console.

* aply.sh: More instructions for initial setup and testing

* Gruntfile: Get consistent with use of package.json datas

* Linux: manually update desktop keys, since macros not available
This commit is contained in:
Scott Nonnenberg 2017-10-30 13:57:13 -07:00 committed by GitHub
parent a3fbb9a6aa
commit c94d4efd18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 158 additions and 34 deletions

View File

@ -8,6 +8,7 @@ install:
- yarn install
script:
- yarn run generate
- yarn prepare-build
- ./node_modules/.bin/build --em.environment=$SIGNAL_ENV --config.mac.bundleVersion='$TRAVIS_BUILD_NUMBER' --publish=never
- ./travis.sh
env:

View File

@ -1,4 +1,5 @@
var path = require('path');
var packageJson = require('./package.json');
module.exports = function(grunt) {
'use strict';
@ -204,23 +205,23 @@ module.exports = function(grunt) {
},
'test-release': {
osx: {
archive: 'mac/Signal.app/Contents/Resources/app.asar',
appUpdateYML: 'mac/Signal.app/Contents/Resources/app-update.yml',
exe: 'mac/Signal.app/Contents/MacOS/Signal'
archive: 'mac/' + packageJson.productName + '.app/Contents/Resources/app.asar',
appUpdateYML: 'mac/' + packageJson.productName + '.app/Contents/Resources/app-update.yml',
exe: 'mac/' + packageJson.productName + '.app/Contents/MacOS/' + packageJson.productName
},
mas: {
archive: 'mas/Signal.app/Contents/Resources/app.asar',
appUpdateYML: 'mac/Signal.app/Contents/Resources/app-update.yml',
exe: 'mas/Signal.app/Contents/MacOS/Signal'
exe: 'mas/' + packageJson.productName + '.app/Contents/MacOS/' + packageJson.productName
},
linux: {
archive: 'linux-unpacked/resources/app.asar',
exe: 'linux-unpacked/signal-desktop'
exe: 'linux-unpacked/' + packageJson.name
},
win: {
archive: 'win-unpacked/resources/app.asar',
appUpdateYML: 'win-unpacked/resources/app-update.yml',
exe: 'win-unpacked/Signal.exe'
exe: 'win-unpacked/' + packageJson.productName + '.exe'
}
},
gitinfo: {} // to be populated by grunt gitinfo
@ -273,7 +274,6 @@ module.exports = function(grunt) {
require('mkdirp').sync('release');
var fs = require('fs');
var done = this.async();
var package_json = grunt.config.get('pkg');
var gitinfo = grunt.config.get('gitinfo');
var https = require('https');
@ -281,7 +281,7 @@ module.exports = function(grunt) {
var keyBase = 'WhisperSystems/Signal-Desktop';
var sha = gitinfo.local.branch.current.SHA;
var files = [{
zip: 'signal-desktop-' + package_json.version + '.zip',
zip: packageJson.name + '-' + packageJson.version + '.zip',
extractedTo: 'linux'
}];
@ -451,7 +451,7 @@ module.exports = function(grunt) {
return app.client.getTitle();
}).then(function (title) {
// Verify the window's title
assert.equal(title, 'Signal');
assert.equal(title, packageJson.productName);
console.log('title ok');
}).then(function () {
assert(app.chromeDriver.logLines.indexOf('NODE_ENV ' + environment) > -1);

View File

@ -30,6 +30,20 @@ a {
document.write('v', window.config.version);
</script>
</div>
<div>
<script>
var states = [];
if (window.config.environment !== 'production') {
states.push(window.config.environment);
}
if (window.config.appInstance) {
states.push(window.config.appInstance);
}
document.write(states.join(' - '));
</script>
</div>
<div>
<a href="https://signal.org">signal.org</a>
</div>

View File

@ -1,12 +1,9 @@
const fs = require('fs');
const path = require('path');
console.log('reading package.json');
const jsonFile = fs.readFileSync(path.join(__dirname, '..', 'package.json'));
const package_json = JSON.parse(jsonFile, 'utf-8');
const environment = package_json.environment || process.env.NODE_ENV || 'development';
const packageJson = require('../package.json');
console.log('configuring');
const environment = packageJson.environment || process.env.NODE_ENV || 'development';
// Set environment vars to configure node-config before requiring it
process.env.NODE_ENV = environment;

View File

@ -1,9 +1,11 @@
const app = require('electron').app;
const path = require('path');
const app = require('electron').app;
const ElectronConfig = require('electron-config');
const config = require('./config');
// use a separate data directory for development
if (config.has('storageProfile')) {
const userData = path.join(

View File

@ -16,6 +16,7 @@ build_script:
- node build\grunt.js
- type package.json | findstr /v certificateSubjectName > temp.json
- move temp.json package.json
- yarn prepare-build
- node_modules\.bin\build --em.environment=%SIGNAL_ENV% --publish=never
test_script:

View File

@ -1,18 +1,40 @@
#!/bin/bash
# Setup:
# Setup - creates the local repo which will be mirrored up to S3, then back-fill it. Your
# future deploys will eliminate all old versions without these backfill steps:
# aptly repo create signal-desktop
# aptly mirror create -ignore-signatures backfill-mirror https://updates.signal.org/desktop/apt xenial
# aptly mirror update -ignore-signatures backfill-mirror
# aptly repo import backfill-mirror signal-desktop signal-desktop signal-desktop-beta
# aptly repo show -with-packages signal-desktop
#
# First run on a machine - uncomment the first two 'aptly publish snapshot' commands,
# comment the other two. Sets up the two publish channels, one local, one to S3.
#
# Testing - comment out the lines with s3:$ENDPOINT to publish only locally. To eliminate
# effects of testing, remove package from repo, then move back to old snapshot:
# aptly repo remove signal-desktop signal-desktop_1.0.35_amd64
# aptly publish switch -gpg-key=57F6FB06 xenial signal-desktop_v1.0.34
#
# Release:
# VERSION=X.X.X ./aptly.sh
# NAME=signal-desktop(-beta) VERSION=X.X.X ./aptly.sh
echo "Releasing $NAME build version $VERSION"
REPO=signal-desktop
DISTRO=xenial
ENDPOINT=signal-desktop-apt # Matches endpoint name in .aptly.conf
DEB_PATH=release
SNAPSHOT=signal-desktop_v$VERSION
GPG_KEYID=57F6FB06
aptly repo add $REPO $DEB_PATH/$REPO\_$VERSION\_*.deb
aptly repo add $REPO release/$NAME\_$VERSION\_*.deb
aptly snapshot create $SNAPSHOT from repo $REPO
# run these two only on first release to a given repo from a given machine
# https://www.aptly.info/doc/aptly/publish/snapshot/
# aptly publish snapshot -gpg-key=$GPG_KEYID $SNAPSHOT
# aptly publish snapshot -gpg-key=$GPG_KEYID -config=.aptly.conf $SNAPSHOT s3:$ENDPOINT:
# these update already-published repos, run every time after that
# https://www.aptly.info/doc/aptly/publish/switch/
aptly publish switch -gpg-key=$GPG_KEYID $DISTRO $SNAPSHOT
aptly publish switch -gpg-key=$GPG_KEYID -config=.aptly.conf $DISTRO s3:$ENDPOINT: $SNAPSHOT

View File

@ -14,6 +14,15 @@
var initialLoadComplete = false;
window.owsDesktopApp = {};
var title = window.config.name;
if (window.config.environment !== 'production') {
title += ' - ' + window.config.environment;
}
if (window.config.appInstance) {
title += ' - ' + window.config.appInstance;
}
window.config.title = window.document.title = title;
// start a background worker for ecc
textsecure.startWorker('js/libsignal-protocol-worker.js');
Whisper.KeyChangeListener.init(textsecure.storage.protocol);

View File

@ -64,10 +64,10 @@
if (newUnreadCount > 0) {
window.setBadgeCount(newUnreadCount);
window.document.title = "Signal (" + newUnreadCount + ")";
window.document.title = window.config.title + " (" + newUnreadCount + ")";
} else {
window.setBadgeCount(0);
window.document.title = "Signal";
window.document.title = window.config.title;
}
},
startPruning: function() {

View File

@ -69,7 +69,12 @@ if (window.console) {
// The mechanics of preparing a log for publish
function getHeader() {
return window.navigator.userAgent + ' node/' + window.config.node_version;
let header = window.navigator.userAgent;
header += ' node/' + window.config.node_version;
header += ' env/' + window.config.environment;
return header;
}
function getLevel(level) {

23
main.js
View File

@ -3,7 +3,7 @@ const url = require('url');
const os = require('os');
const _ = require('lodash');
const electron = require('electron')
const electron = require('electron');
const BrowserWindow = electron.BrowserWindow;
const app = electron.app;
@ -11,20 +11,26 @@ const ipc = electron.ipcMain;
const Menu = electron.Menu;
const shell = electron.shell;
const packageJson = require('./package.json');
const autoUpdate = require('./app/auto_update');
const windowState = require('./app/window_state');
console.log('setting AUMID');
app.setAppUserModelId('org.whispersystems.signal-desktop')
const aumid = 'org.whispersystems.' + packageJson.name;
console.log('setting AUMID to ' + aumid);
app.setAppUserModelId(aumid);
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;
const config = require("./app/config");
if (config.environment === 'production' && !process.mas) {
// Very important to put before the single instance check, since it is based on the
// userData directory.
const userConfig = require('./app/user_config');
if (!process.mas) {
console.log('making app single instance');
var shouldQuit = app.makeSingleInstance(function(commandLine, workingDirectory) {
// Someone tried to run a second instance, we should focus our window
@ -36,16 +42,15 @@ if (config.environment === 'production' && !process.mas) {
});
if (shouldQuit) {
console.log('quitting');
console.log('quitting; we are the second instance');
app.quit();
return;
}
}
const userConfig = require('./app/user_config');
const logging = require('./app/logging');
// this must be after we set up appPath in user_config.js
// This must be after we set up appPath in user_config.js, so we know where logs go
logging.initialize();
const logger = logging.getLogger();
@ -60,6 +65,7 @@ function prepareURL(pathSegments) {
protocol: 'file:',
slashes: true,
query: {
name: packageJson.productName,
locale: locale.name,
version: app.getVersion(),
buildExpiration: config.get('buildExpiration'),
@ -69,6 +75,7 @@ function prepareURL(pathSegments) {
environment: config.environment,
node_version: process.versions.node,
hostname: os.hostname(),
appInstance: process.env.NODE_APP_INSTANCE,
}
})
}

View File

@ -41,6 +41,7 @@
"build": "build --em.environment=$SIGNAL_ENV",
"dist": "npm run generate && npm run build",
"pack": "npm run generate && npm run build -- --dir",
"prepare-build": "node prepare_build.js",
"pack-prod": "SIGNAL_ENV=production npm run pack",
"dist-prod": "SIGNAL_ENV=production npm run dist",
"dist-prod-all": "SIGNAL_ENV=production npm run dist -- -mwl",
@ -51,13 +52,13 @@
"prep-release": "npm run generate && grunt prep-release && npm run build-release && npm run build-mas-release && grunt test-release",
"release-mac": "npm run build-release -- -m --prepackaged release/mac/Signal.app --publish=always",
"release-win": "npm run build-release -- -w --prepackaged release/windows --publish=always",
"release-lin": "npm run build-release -- -l --prepackaged release/linux && VERSION=$npm_package_version ./aptly.sh",
"release-lin": "npm run build-release -- -l --prepackaged release/linux && NAME=$npm_package_name VERSION=$npm_package_version ./aptly.sh",
"release": "npm run release-mac && npm run release-win && npm run release-lin"
},
"build": {
"appId": "org.whispersystems.signal-desktop",
"mac": {
"artifactName": "${productName}-mac-${version}.${ext}",
"artifactName": "${name}-mac-${version}.${ext}",
"category": "public.app-category.social-networking",
"icon": "build/icons/mac/icon.icns",
"publish": [
@ -81,7 +82,7 @@
},
"win": {
"asarUnpack": "node_modules/spellchecker/vendor/hunspell_dictionaries",
"artifactName": "${productName}-win-${version}.${ext}",
"artifactName": "${name}-win-${version}.${ext}",
"certificateSubjectName": "Signal",
"publisherName": "Signal (Quiet Riddle Ventures, LLC)",
"icon": "build/icons/win/icon.ico",

65
prepare_build.js Normal file
View File

@ -0,0 +1,65 @@
const fs = require('fs');
const _ = require('lodash');
const packageJson = require('./package.json');
const version = packageJson.version;
const beta = /beta/;
// You might be wondering why this file is necessary. It comes down to our desire to allow
// side-by-side installation of production and beta builds. Electron-Builder uses
// top-level data from package.json for many things, like the executable name, the
// debian package name, the install directory under /opt on linux, etc. We tried
// adding the ${channel} macro to these values, but Electron-Builder didn't like that.
if (!beta.test(version)) {
return;
}
console.log('prepare_build: updating package.json for beta build');
// -------
const NAME_PATH = 'name';
const PRODUCTION_NAME = 'signal-desktop';
const BETA_NAME = 'signal-desktop-beta';
const PRODUCT_NAME_PATH = 'productName';
const PRODUCTION_PRODUCT_NAME = 'Signal';
const BETA_PRODUCT_NAME = 'Signal Beta';
const APP_ID_PATH = 'build.appId';
const PRODUCTION_APP_ID = 'org.whispersystems.signal-desktop';
const BETA_APP_ID = 'org.whispersystems.signal-desktop-beta';
const STARTUP_WM_CLASS_PATH = 'build.linux.desktop.StartupWMClass';
const PRODUCTION_STARTUP_WM_CLASS = 'Signal';
const BETA_STARTUP_WM_CLASS = 'Signal Beta';
// -------
function checkValue(object, objectPath, expected) {
const actual = _.get(object, objectPath)
if (actual !== expected) {
throw new Error(objectPath + ' was ' + actual + '; expected ' + expected);
}
}
// ------
checkValue(packageJson, NAME_PATH, PRODUCTION_NAME);
checkValue(packageJson, PRODUCT_NAME_PATH, PRODUCTION_PRODUCT_NAME);
checkValue(packageJson, APP_ID_PATH, PRODUCTION_APP_ID);
checkValue(packageJson, STARTUP_WM_CLASS_PATH, PRODUCTION_STARTUP_WM_CLASS);
// -------
_.set(packageJson, NAME_PATH, BETA_NAME);
_.set(packageJson, PRODUCT_NAME_PATH, BETA_PRODUCT_NAME);
_.set(packageJson, APP_ID_PATH, BETA_APP_ID);
_.set(packageJson, STARTUP_WM_CLASS_PATH, BETA_STARTUP_WM_CLASS);
// -------
fs.writeFileSync('./package.json', JSON.stringify(packageJson, null, ' '));