Skip to content

Commit fc06cc1

Browse files
authored
Back up to previous prompts (#48)
* Tweak installation, and allow for master releases which don't trigger auto-update. * Options to handle grief from polkit. * New build input method to allow using up arrow to back up to a previous prompt.
1 parent dc4afd1 commit fc06cc1

9 files changed

Lines changed: 156 additions & 57 deletions

File tree

build.ts

Lines changed: 116 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@ import * as Chalk from 'chalk';
22
import { exec } from 'child_process';
33
import * as copyfiles from 'copyfiles';
44
import * as fs from 'fs';
5+
import * as readline from 'readline';
6+
import { Key } from 'readline';
57
import { asLines, isFunction, isNumber, isObject, isString, processMillis, toBoolean, toNumber } from '@tubular/util';
68
import * as path from 'path';
79
import { convertPinToGpio } from './server/src/rpi-pin-conversions';
810
import { ErrorMode, getSudoUser, getUserHome, monitorProcess, monitorProcessLines, sleep, spawn } from './server/src/process-util';
911
import { promisify } from 'util';
1012

13+
readline.emitKeypressEvents(process.stdin);
14+
process.stdin.setRawMode(true);
15+
1116
const CHECK_MARK = '\u2714';
1217
const FAIL_MARK = '\u2718';
1318
const SPIN_CHARS = '|/-\\';
@@ -29,6 +34,7 @@ let doStdDeploy = false;
2934
let doDedicated = false;
3035
let doLaunch = false;
3136
let doReboot = false;
37+
let noStop = false;
3238
let prod = false;
3339
let viaBash = false;
3440
let interactive = false;
@@ -60,7 +66,7 @@ chalk.paleYellow = chalk.hex('#FFFFAA');
6066
let backspace = '\x08';
6167
let sol = '\x1B[1G';
6268
let trailingSpace = ' '; // Two spaces
63-
let totalSteps = 3;
69+
let totalSteps = 2;
6470
let currentStep = 0;
6571
const settings: Record<string, string> = {
6672
AWC_ALLOW_ADMIN: 'false',
@@ -81,6 +87,7 @@ const settingsPath = '/etc/default/weatherService';
8187
const rpiSetupStuff = path.join(__dirname, 'raspberry_pi_setup');
8288
const serviceSrc = rpiSetupStuff + '/weatherService';
8389
const serviceDst = '/etc/init.d/.';
90+
const serviceDstFull = '/etc/init.d/weatherService';
8491
const fontSrc = rpiSetupStuff + '/fonts/';
8592
const fontDst = '/usr/local/share/fonts/';
8693
let chromium = 'chromium';
@@ -181,6 +188,9 @@ process.argv.forEach(arg => {
181188
onlyOnRaspberryPi.push(arg);
182189
onlyDedicated.push(arg);
183190
break;
191+
case '--nostop':
192+
noStop = true;
193+
break;
184194
case '-p':
185195
prod = true;
186196
break;
@@ -227,21 +237,24 @@ process.argv.forEach(arg => {
227237

228238
console.error('Unrecognized option "' + chalk.redBright(arg) + '"');
229239
console.log(helpMsg);
230-
process.exit(0);
240+
process.exit(1);
231241
}
232242
}
233243
});
234244

245+
if (noStop)
246+
doReboot = doLaunch = false;
247+
235248
if (!treatAsRaspberryPi && onlyOnRaspberryPi.length > 0) {
236249
onlyOnRaspberryPi.forEach(opt =>
237250
console.error(chalk.redBright(opt) + ' option is only valid on Raspberry Pi'));
238-
process.exit(0);
251+
process.exit(1);
239252
}
240253

241254
if (!doDedicated && onlyDedicated.length > 0) {
242255
onlyDedicated.forEach(opt =>
243256
console.error(chalk.redBright(opt) + ' option is only valid when used with the --ddev or -i options'));
244-
process.exit(0);
257+
process.exit(1);
245258
}
246259

247260
if (treatAsRaspberryPi) {
@@ -289,14 +302,56 @@ if (treatAsRaspberryPi) {
289302
if (!isRaspberryPi && doAcu)
290303
console.warn(chalk.yellow('Warning: this setup will only generate fake wireless sensor data'));
291304

292-
async function readLine(): Promise<string> {
305+
async function readUserInput(): Promise<string> {
293306
return new Promise<string>(resolve => {
294-
const callback = (data: any) => {
295-
process.stdin.off('data', callback);
296-
resolve(data.toString().trim());
307+
let buffer = '';
308+
let length = 0;
309+
const clearLine = () => write('\x08 \x08'.repeat(length));
310+
311+
const callback = (ch: string, key: Key) => {
312+
if (ch === '\x03') { // ctrl-C
313+
write('^C\n');
314+
process.exit(130);
315+
}
316+
else if (ch === '\x15') { // ctrl-U
317+
clearLine();
318+
length = 0;
319+
}
320+
else if (key.name === 'enter' || key.name === 'return') {
321+
write('\n');
322+
process.stdin.off('keypress', callback);
323+
resolve(buffer.substr(0, length).trim());
324+
}
325+
else if (key.name === 'backspace' || key.name === 'left') {
326+
if (length > 0) {
327+
write('\x08 \x08');
328+
--length;
329+
}
330+
}
331+
else if (key.name === 'delete') {
332+
if (length > 0) {
333+
write('\x08 \x08');
334+
buffer = buffer.substr(0, --length) + buffer.substr(length + 1);
335+
}
336+
}
337+
else if (key.name === 'up') {
338+
clearLine();
339+
write('\n');
340+
process.stdin.off('keypress', callback);
341+
resolve('\x18');
342+
}
343+
else if (key.name === 'right') {
344+
if (length < buffer.length) {
345+
write(buffer.charAt(length++));
346+
}
347+
}
348+
else if (ch != null && ch >= ' ' && !key.ctrl && !key.meta) {
349+
write(ch);
350+
buffer = buffer.substr(0, length) + ch + buffer.substr(length++);
351+
}
297352
};
298353

299-
process.stdin.on('data', callback);
354+
process.stdin.on('keypress', callback);
300355
});
301356
}
302357

@@ -611,7 +666,7 @@ function finalActionValidate(s: string): boolean {
611666
const finalAction = (doReboot ? 'R' : doLaunch ? 'L' : 'N');
612667
const finalOptions = '(l/r/n/)'.replace(finalAction.toLowerCase(), finalAction);
613668

614-
const questions = [
669+
let questions = [
615670
{ prompt: 'Perform initial update/upgrade?', ask: true, yn: true, deflt: doUpdateUpgrade ? 'Y' : 'N', validate: upgradeValidate },
616671
{ prompt: 'Perform npm install? (N option for debug only)', ask: true, yn: true, deflt: doNpmI ? 'Y' : 'N', validate: npmIValidate },
617672
{ name: 'AWC_PORT', prompt: 'HTTP server port.', ask: true, validate: portValidate },
@@ -654,6 +709,9 @@ if (NO_MORE_DARK_SKY) {
654709
questions.splice(7, 1);
655710
}
656711

712+
if (noStop)
713+
questions = questions.slice(0, -1);
714+
657715
async function promptForConfiguration(): Promise<void> {
658716
console.log(chalk.cyan(sol + '- Configuration -'));
659717

@@ -676,9 +734,13 @@ async function promptForConfiguration(): Promise<void> {
676734
write(': ');
677735
}
678736

679-
const response = await readLine();
737+
const response = await readUserInput();
680738

681-
if (response) {
739+
if (response === '\x18') {
740+
i = Math.max(i - 2, -1);
741+
continue;
742+
}
743+
else if (response) {
682744
const validation = q.validate ? q.validate(response) : true;
683745

684746
if (isString(validation))
@@ -832,7 +894,7 @@ async function doServiceDeployment(): Promise<void> {
832894
showStep();
833895
write('Create or redeploy weatherService' + trailingSpace);
834896
await monitorProcess(spawn('cp', [serviceSrc, serviceDst], { shell: true }), spin, ErrorMode.ANY_ERROR);
835-
await monitorProcess(spawn('chmod', ['+x', serviceDst], { shell: true }), spin, ErrorMode.ANY_ERROR);
897+
await monitorProcess(spawn('chmod', ['+x', serviceDstFull], { shell: true }), spin, ErrorMode.ANY_ERROR);
836898

837899
const settingsText =
838900
`# If you edit AWC_PORT below, be sure to update\n# ${userHome}/${autostartDst}/autostart accordingly.\n` +
@@ -897,7 +959,12 @@ async function doServiceDeployment(): Promise<void> {
897959
{ shell: true }), spin, ErrorMode.ANY_ERROR);
898960
await monitorProcess(spawn('chmod', uid, ['+x', autostartDir + '/autostart*'],
899961
{ shell: true }), spin, ErrorMode.ANY_ERROR);
900-
await monitorProcess(spawn('service', ['weatherService', 'start']), spin);
962+
963+
if (noStop)
964+
console.log(backspace + trailingSpace + '\n\nReboot to complete set-up.');
965+
else
966+
await monitorProcess(spawn('service', ['weatherService', 'start']), spin);
967+
901968
stepDone();
902969
}
903970

@@ -910,7 +977,7 @@ async function doServiceDeployment(): Promise<void> {
910977

911978
if (isRaspberryPi && (nodeVersion < 10 || nodeVersion > 14)) {
912979
console.error(chalk.redBright(`Node.js version 10.x-14.x required. Version ${nodeVersionStr} found.`));
913-
process.exit(0);
980+
process.exit(1);
914981
}
915982

916983
if (treatAsRaspberryPi && !isRaspberryPi) {
@@ -919,7 +986,7 @@ async function doServiceDeployment(): Promise<void> {
919986

920987
if (!isDebian || !isLxde) {
921988
console.error(chalk.redBright('--tarp option (Treat As Raspberry Pi) only available for Linux Debian with LXDE'));
922-
process.exit(0);
989+
process.exit(1);
923990
}
924991
}
925992

@@ -929,6 +996,7 @@ async function doServiceDeployment(): Promise<void> {
929996
if (interactive)
930997
await promptForConfiguration();
931998

999+
totalSteps += noStop ? 0 : 1;
9321000
totalSteps += (doNpmI || !fs.existsSync('node_modules') || !fs.existsSync('package-lock.json')) ? 1 : 0;
9331001
totalSteps += (doNpmI || !fs.existsSync('server/node_modules') || !fs.existsSync('server/package-lock.json')) ? 1 : 0;
9341002
totalSteps += doAcu ? 1 : 0;
@@ -945,10 +1013,34 @@ async function doServiceDeployment(): Promise<void> {
9451013
if (doDedicated) {
9461014
totalSteps += 9 + (doUpdateUpgrade ? 1 : 0);
9471015
console.log(chalk.cyan(sol + '- Dedicated device setup -'));
948-
showStep();
949-
write('Stopping weatherService if currently running' + trailingSpace);
950-
await monitorProcess(spawn('service', ['weatherService', 'stop']), spin, ErrorMode.NO_ERRORS);
951-
stepDone();
1016+
1017+
if (!noStop) {
1018+
showStep();
1019+
write('Stopping weatherService if currently running' + trailingSpace);
1020+
1021+
try {
1022+
await monitorProcess(spawn('service', ['weatherService', 'stop']), spin, ErrorMode.ANY_ERROR);
1023+
}
1024+
catch (err) {
1025+
const msg = err.message || err.toString();
1026+
1027+
// Grief from polkit?
1028+
if (/Interactive authentication required/i.test(msg)) {
1029+
console.log(backspace + trailingSpace);
1030+
console.error(err);
1031+
console.log('\npolkit is requiring interactive authentication to stop weatherService.');
1032+
console.log('Please enter "sudo service stop weatherService" at the prompt below.');
1033+
console.log('\nWhen that is done, restart this installation with either: ');
1034+
console.log(' sudo ./build.sh --nostop -i (for interactive set-up)');
1035+
console.log(' sudo ./build.sh --nostop -ddev (for automated set-up)');
1036+
process.exit(1);
1037+
}
1038+
// TODO: else? Probably just weatherService not installed yet
1039+
// Might check /unrecognized service|not loaded/ to be more certain.
1040+
}
1041+
1042+
stepDone();
1043+
}
9521044

9531045
if (doUpdateUpgrade) {
9541046
showStep();
@@ -965,6 +1057,7 @@ async function doServiceDeployment(): Promise<void> {
9651057
for (let i = 0; i < 2; ++i) {
9661058
try {
9671059
await install('forever', true, false, i > 0);
1060+
break;
9681061
}
9691062
catch {
9701063
if (i > 0)
@@ -1041,8 +1134,10 @@ async function doServiceDeployment(): Promise<void> {
10411134
showStep();
10421135
write('Press any key to stop reboot:' + trailingSpace);
10431136

1044-
if (!(await sleep(3000, spin, true)))
1137+
if (!(await sleep(5000, spin, true))) {
1138+
console.log();
10451139
exec('reboot');
1140+
}
10461141
else
10471142
console.log();
10481143
}

package-lock.json

Lines changed: 28 additions & 26 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "aw-clock",
3-
"version": "2.9.0",
3+
"version": "2.9.1",
44
"license": "MIT",
55
"author": "Kerry Shetline <kerry@shetline.com>",
66
"scripts": {

0 commit comments

Comments
 (0)