sliftutils 1.7.126 → 1.7.127
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/testnotify.js +9 -0
- package/package.json +5 -3
- package/security/authorizedKeys/authorizedKeys.ts +49 -9
- package/security/authorizedKeys/daemon/authLog.ts +74 -5
- package/security/authorizedKeys/daemon/changes.ts +26 -0
- package/security/authorizedKeys/daemon/daemon.ts +51 -24
- package/security/authorizedKeys/daemon/revocation.ts +46 -23
- package/security/authorizedKeys/daemon/rootKeys.ts +88 -25
- package/security/authorizedKeys/daemon/sessions.ts +139 -0
- package/security/authorizedKeys/daemon/state.ts +16 -2
- package/security/authorizedKeys/dist/authorizedKeys.ts.cache +49 -11
- package/security/authorizedKeys/dist/secureSSH.ts.cache +18 -31
- package/security/authorizedKeys/dist/unrevoke.ts.cache +36 -14
- package/security/authorizedKeys/secureSSH.ts +77 -61
- package/security/authorizedKeys/unrevoke.ts +38 -12
- package/security/helpers/remoteSSH.ts +17 -6
- package/security/notifications/discord.ts +18 -1
- package/security/notifications/dist/discord.ts.cache +21 -4
- package/security/notifications/setupNotify.ts +17 -8
- package/security/notifications/testNotify.ts +48 -0
- package/security/signedFiles/dist/manifest.ts.cache +21 -4
- package/security/signedFiles/dist/signFiles.ts.cache +48 -13
- package/security/signedFiles/manifest.ts +19 -2
- package/security/signedFiles/signFiles.ts +51 -12
|
@@ -8,7 +8,7 @@ import { deriveRevokeKey, REVOKE_KEY_LABEL, revokeRepoURL } from "./revokeSource
|
|
|
8
8
|
import { revokedKeysInRepo } from "./unrevoke";
|
|
9
9
|
import { expandHome } from "../helpers/paths";
|
|
10
10
|
import { spawnPromise } from "../helpers/spawn";
|
|
11
|
-
import { readRemoteFile, remoteCommandExists, runOverSSH, SUDO_PREAMBLE, writeRemoteFile } from "../helpers/remoteSSH";
|
|
11
|
+
import { describeHost, readRemoteFile, remoteCommandExists, runOverSSH, SUDO_PREAMBLE, THIS_MACHINE, writeRemoteFile } from "../helpers/remoteSSH";
|
|
12
12
|
|
|
13
13
|
const SERVICE_SOURCE = path.join(__dirname, "daemon", "portsecure.service");
|
|
14
14
|
// The daemon runs from a checkout of this repo on the host, rather than from a copy we upload, so
|
|
@@ -22,12 +22,16 @@ const ROOT_AUTHORIZED_KEYS = "/root/.ssh/authorized_keys";
|
|
|
22
22
|
const SERVICE_NAME = "portsecure";
|
|
23
23
|
const MAX_ERROR_BODY_LENGTH = 500;
|
|
24
24
|
const VERBS = ["add", "remove", "list", "update"];
|
|
25
|
-
// The
|
|
25
|
+
// The host is optional, and without one everything happens on this machine. The repo url is
|
|
26
|
+
// optional too, and defaults to the repo the command is run from.
|
|
26
27
|
const USAGE = `Usage:
|
|
27
|
-
yarn securessh
|
|
28
|
-
yarn securessh
|
|
29
|
-
yarn securessh
|
|
30
|
-
yarn securessh
|
|
28
|
+
yarn securessh [host] add <repo-private-key> [repo-url]
|
|
29
|
+
yarn securessh [host] remove [repo-url]
|
|
30
|
+
yarn securessh [host] list
|
|
31
|
+
yarn securessh [host] update
|
|
32
|
+
|
|
33
|
+
With no host it acts on this machine, and still installs from github rather than from wherever
|
|
34
|
+
this was run.`;
|
|
31
35
|
|
|
32
36
|
async function pathExists(filePath: string) {
|
|
33
37
|
try {
|
|
@@ -176,7 +180,19 @@ async function ensureRevokeReposExist(repoSources: string[]) {
|
|
|
176
180
|
let revokeURL = revokeRepoURL(repoURL);
|
|
177
181
|
let temporaryDirectory = await fs.mkdtemp(path.join(os.tmpdir(), "portsecure-revoke-"));
|
|
178
182
|
let checkoutPath = path.join(temporaryDirectory, "repo");
|
|
179
|
-
|
|
183
|
+
// On a machine that is already a portsecure host, the source's deploy key is right there,
|
|
184
|
+
// and the key derived from it is the one credential that repo is guaranteed to accept.
|
|
185
|
+
// Anywhere else, a person is running this and has their own access.
|
|
186
|
+
let ownKey = "";
|
|
187
|
+
if (await pathExists(sourceKeyPath(repoURL))) {
|
|
188
|
+
let derived = deriveRevokeKey(await fs.readFile(sourceKeyPath(repoURL), "utf8"));
|
|
189
|
+
ownKey = path.join(temporaryDirectory, "key");
|
|
190
|
+
await fs.writeFile(ownKey, derived.privateKeyFile, { mode: 0o600 });
|
|
191
|
+
}
|
|
192
|
+
let cloneRevoke = async (allowFailure: boolean) => ownKey
|
|
193
|
+
&& await gitWithKey({ keyPath: ownKey, args: ["clone", revokeURL, checkoutPath], allowFailure })
|
|
194
|
+
|| await runLocal({ command: "git", args: ["clone", revokeURL, checkoutPath], allowFailure });
|
|
195
|
+
let clone = await cloneRevoke(true);
|
|
180
196
|
if (clone.status !== 0) {
|
|
181
197
|
await fs.rm(temporaryDirectory, { recursive: true, force: true });
|
|
182
198
|
throw new Error(
|
|
@@ -194,10 +210,15 @@ async function ensureRevokeReposExist(repoSources: string[]) {
|
|
|
194
210
|
for (let args of [
|
|
195
211
|
["-C", checkoutPath, "add", "-A"],
|
|
196
212
|
["-C", checkoutPath, "-c", "user.email=portsecure@localhost", "-c", "user.name=portsecure", "commit", "-m", "initialise revoke repo"],
|
|
197
|
-
["-C", checkoutPath, "push", "origin", "HEAD"],
|
|
198
213
|
]) {
|
|
199
214
|
await runLocal({ command: "git", args });
|
|
200
215
|
}
|
|
216
|
+
let push = ["-C", checkoutPath, "push", "origin", "HEAD"];
|
|
217
|
+
if (ownKey) {
|
|
218
|
+
await gitWithKey({ keyPath: ownKey, args: push });
|
|
219
|
+
} else {
|
|
220
|
+
await runLocal({ command: "git", args: push });
|
|
221
|
+
}
|
|
201
222
|
}
|
|
202
223
|
await fs.rm(temporaryDirectory, { recursive: true, force: true });
|
|
203
224
|
console.log(`${revokeURL} is ready`);
|
|
@@ -296,14 +317,24 @@ async function installDaemon(config: { host: string; hostLabel: string; repoSour
|
|
|
296
317
|
|
|
297
318
|
// The checkout is brought to the latest commit rather than a copy being pushed, so what runs on
|
|
298
319
|
// the host is exactly what is on github.
|
|
299
|
-
console.log(`Updating ${REMOTE_CHECKOUT_PATH} on ${host}`);
|
|
320
|
+
console.log(`Updating ${REMOTE_CHECKOUT_PATH} on ${describeHost(host)}`);
|
|
300
321
|
await runOverSSH({
|
|
301
322
|
host,
|
|
302
323
|
script: `${SUDO_PREAMBLE}
|
|
303
324
|
set -e
|
|
304
325
|
$SUDO mkdir -p "${path.posix.dirname(REMOTE_CHECKOUT_PATH)}"
|
|
305
326
|
if $SUDO test -d "${REMOTE_CHECKOUT_PATH}/.git"; then
|
|
327
|
+
# Stashed first, the way machine-alwaysup does it. Anything sitting modified in a host's
|
|
328
|
+
# checkout is either an accident or somebody editing the daemon in place, and either way it is
|
|
329
|
+
# worth keeping rather than quietly erasing. The identity is passed inline because a host has
|
|
330
|
+
# no git config of its own and stash writes a commit.
|
|
331
|
+
$SUDO git -C "${REMOTE_CHECKOUT_PATH}" add --all
|
|
332
|
+
$SUDO git -C "${REMOTE_CHECKOUT_PATH}" -c user.email=portsecure@localhost -c user.name=portsecure stash
|
|
306
333
|
$SUDO git -C "${REMOTE_CHECKOUT_PATH}" fetch --prune origin
|
|
334
|
+
# A stash and a pull cannot cross a branch that has diverged, and a drifted checkout must not be
|
|
335
|
+
# able to leave a host running old code, so it is put on the remote's state rather than merged
|
|
336
|
+
# with it. set-head so that is the remote's default branch now, not the one it had at clone.
|
|
337
|
+
$SUDO git -C "${REMOTE_CHECKOUT_PATH}" remote set-head origin --auto
|
|
307
338
|
$SUDO git -C "${REMOTE_CHECKOUT_PATH}" reset --hard origin/HEAD
|
|
308
339
|
else
|
|
309
340
|
$SUDO rm -rf "${REMOTE_CHECKOUT_PATH}"
|
|
@@ -368,7 +399,7 @@ async function requireRemoteWebhook(host: string) {
|
|
|
368
399
|
throw new Error(
|
|
369
400
|
`Expected a Discord webhook at ${DEFAULT_WEBHOOK_FILE_PATH} on ${host}, no such file exists.`
|
|
370
401
|
+ ` The daemon will not start without one.\n`
|
|
371
|
-
+ `Set it up first:\n yarn setupnotify ${host} <discord-webhook-url
|
|
402
|
+
+ `Set it up first:\n yarn setupnotify ${host} <discord-webhook-url>`.replace(" <discord", host && " <discord" || "<discord")
|
|
372
403
|
);
|
|
373
404
|
}
|
|
374
405
|
return parseWebhookFile({ contents, sourceName: `${host}:${DEFAULT_WEBHOOK_FILE_PATH}` });
|
|
@@ -392,18 +423,22 @@ async function addSource(config: { host: string; keyPath: string; repoURL: strin
|
|
|
392
423
|
|
|
393
424
|
let remoteConfig = await readRemoteConfig(host);
|
|
394
425
|
if (remoteConfig.repoSources.includes(repoURL)) {
|
|
395
|
-
console.log(`${host} already has ${repoURL}, refreshing its key and the daemon.`);
|
|
426
|
+
console.log(`${describeHost(host)} already has ${repoURL}, refreshing its key and the daemon.`);
|
|
396
427
|
}
|
|
397
428
|
|
|
398
|
-
// The merged result is what root ends up with, so our own key has to be somewhere in it.
|
|
399
|
-
|
|
400
|
-
let ourFingerprint =
|
|
429
|
+
// The merged result is what root ends up with, so our own key has to be somewhere in it. On
|
|
430
|
+
// this machine there is no ssh session to preserve, so there is nothing to check.
|
|
431
|
+
let ourFingerprint = "";
|
|
432
|
+
if (host) {
|
|
433
|
+
console.log(`Checking our access to ${describeHost(host)} survives the merged keys`);
|
|
434
|
+
ourFingerprint = await findAuthenticatingFingerprint(host);
|
|
435
|
+
}
|
|
401
436
|
let inspectionPath = await cloneRepoForInspection({ repoURL, keyPath });
|
|
402
437
|
let newKeys = await readRepoKeys(inspectionPath);
|
|
403
438
|
// Whatever is applied on the host came from the existing sources, so it stays in the merge.
|
|
404
439
|
let existingKeys = normalizeKeys(await readRemoteFile({ host, filePath: ROOT_AUTHORIZED_KEYS }) || "");
|
|
405
440
|
let mergedFingerprints = await fingerprintKeys([...existingKeys, ...newKeys]);
|
|
406
|
-
if (!mergedFingerprints.includes(ourFingerprint)) {
|
|
441
|
+
if (ourFingerprint && !mergedFingerprints.includes(ourFingerprint)) {
|
|
407
442
|
throw new Error(
|
|
408
443
|
`Expected the key we use for ${host} to be in the merged keys, it is not.\n`
|
|
409
444
|
+ `Ours: ${ourFingerprint}\n`
|
|
@@ -412,7 +447,9 @@ async function addSource(config: { host: string; keyPath: string; repoURL: strin
|
|
|
412
447
|
+ ` lock you out of ${host}. Add your public key to ${repoURL} first.`
|
|
413
448
|
);
|
|
414
449
|
}
|
|
415
|
-
|
|
450
|
+
if (ourFingerprint) {
|
|
451
|
+
console.log(`Our key ${ourFingerprint} is in the merged keys, access will survive.`);
|
|
452
|
+
}
|
|
416
453
|
|
|
417
454
|
await ensureRevokeRepo({ keyPath, repoURL });
|
|
418
455
|
|
|
@@ -428,7 +465,7 @@ async function addSource(config: { host: string; keyPath: string; repoURL: strin
|
|
|
428
465
|
}
|
|
429
466
|
|
|
430
467
|
let webhookURL = await requireRemoteWebhook(host);
|
|
431
|
-
console.log(`${host} notifies ${webhookURL}`);
|
|
468
|
+
console.log(`${describeHost(host)} notifies ${webhookURL}`);
|
|
432
469
|
|
|
433
470
|
await writeRemoteFile({
|
|
434
471
|
host,
|
|
@@ -441,7 +478,7 @@ async function addSource(config: { host: string; keyPath: string; repoURL: strin
|
|
|
441
478
|
let repoSources = remoteConfig.repoSources.filter(source => source !== repoURL);
|
|
442
479
|
repoSources.push(repoURL);
|
|
443
480
|
await installDaemon({ host, hostLabel: remoteConfig.hostLabel, repoSources });
|
|
444
|
-
console.log(`${repoURL} added to ${host}. ${repoSources.length} source(s) now merged.`);
|
|
481
|
+
console.log(`${repoURL} added to ${describeHost(host)}. ${repoSources.length} source(s) now merged.`);
|
|
445
482
|
}
|
|
446
483
|
|
|
447
484
|
async function removeSource(config: { host: string; repoURL: string }) {
|
|
@@ -455,9 +492,10 @@ async function removeSource(config: { host: string; repoURL: string }) {
|
|
|
455
492
|
}
|
|
456
493
|
let repoSources = remoteConfig.repoSources.filter(source => source !== repoURL);
|
|
457
494
|
|
|
458
|
-
|
|
495
|
+
// On this machine there is no ssh session to preserve, so there is nothing to check.
|
|
496
|
+
if (repoSources.length && host) {
|
|
459
497
|
// The keys left over are what root gets, so our own key has to be among them.
|
|
460
|
-
console.log(`Checking our access to ${host} survives without ${repoURL}`);
|
|
498
|
+
console.log(`Checking our access to ${describeHost(host)} survives without ${repoURL}`);
|
|
461
499
|
let ourFingerprint = await findAuthenticatingFingerprint(host);
|
|
462
500
|
let remainingKeys: string[] = [];
|
|
463
501
|
for (let source of repoSources) {
|
|
@@ -485,47 +523,15 @@ $SUDO rm -f "${sourceKeyPath(repoURL)}"
|
|
|
485
523
|
$SUDO rm -rf "${sourceRepoPath(repoURL)}"`,
|
|
486
524
|
});
|
|
487
525
|
await installDaemon({ host, hostLabel: remoteConfig.hostLabel, repoSources });
|
|
488
|
-
console.log(`${repoURL} removed from ${host}. ${repoSources.length} source(s) left.`);
|
|
526
|
+
console.log(`${repoURL} removed from ${describeHost(host)}. ${repoSources.length} source(s) left.`);
|
|
489
527
|
}
|
|
490
528
|
|
|
491
529
|
/** Answers "who can log into this box, and which repo says so". The paths the daemon uses are
|
|
492
530
|
left out on purpose, they are plumbing rather than something to act on. */
|
|
493
|
-
/** The daemon is installed from this checkout, so the checkout is brought up to date first.
|
|
494
|
-
Anything that stops the pull - a conflict, local commits, no upstream - stops the update too,
|
|
495
|
-
rather than quietly installing whatever happened to be on disk. */
|
|
496
|
-
async function pullLocalCheckout() {
|
|
497
|
-
let topLevel = await spawnPromise({ command: "git", args: ["rev-parse", "--show-toplevel"], cwd: __dirname });
|
|
498
|
-
if (topLevel.status !== 0) {
|
|
499
|
-
throw new Error(
|
|
500
|
-
`Expected ${__dirname} to be inside a git checkout, it is not.`
|
|
501
|
-
+ ` update installs the daemon from this checkout, so it has to be one.`
|
|
502
|
-
);
|
|
503
|
-
}
|
|
504
|
-
let repoPath = topLevel.stdout.trim();
|
|
505
|
-
let pull = await spawnPromise({ command: "git", args: ["pull", "--ff-only"], cwd: repoPath });
|
|
506
|
-
if (pull.status !== 0) {
|
|
507
|
-
throw new Error(
|
|
508
|
-
`Expected git pull in ${repoPath} to succeed, it did not, so nothing was installed.\n`
|
|
509
|
-
+ `${(pull.stdout + pull.stderr).trim().slice(0, MAX_ERROR_BODY_LENGTH)}`
|
|
510
|
-
);
|
|
511
|
-
}
|
|
512
|
-
// The host installs from github, not from here, so local commits that have not been pushed
|
|
513
|
-
// are not what it will run.
|
|
514
|
-
let local = await spawnPromise({ command: "git", args: ["rev-parse", "HEAD"], cwd: repoPath });
|
|
515
|
-
let remote = await spawnPromise({ command: "git", args: ["rev-parse", "@{u}"], cwd: repoPath });
|
|
516
|
-
if (local.stdout.trim() !== remote.stdout.trim()) {
|
|
517
|
-
console.log(`WARNING: ${repoPath} has commits that are not pushed. The host installs from github,`);
|
|
518
|
-
console.log(` so it will run the pushed version, not what is here.`);
|
|
519
|
-
}
|
|
520
|
-
console.log(`Pulled ${repoPath}`);
|
|
521
|
-
}
|
|
522
531
|
|
|
523
532
|
/** Pushes the current daemon onto a host that already has one, for when this code has moved on.
|
|
524
533
|
Nothing about which keys the host trusts is touched. */
|
|
525
534
|
async function updateDaemon(host: string) {
|
|
526
|
-
// Pulled before anything else, so a checkout that cannot be brought up to date fails here
|
|
527
|
-
// rather than after we have already started changing the host.
|
|
528
|
-
await pullLocalCheckout();
|
|
529
535
|
let contents = await readRemoteFile({ host, filePath: REMOTE_CONFIG_PATH });
|
|
530
536
|
if (!contents) {
|
|
531
537
|
throw new Error(
|
|
@@ -538,16 +544,21 @@ async function updateDaemon(host: string) {
|
|
|
538
544
|
await requireRemoteWebhook(host);
|
|
539
545
|
await ensureRevokeReposExist(repoSources);
|
|
540
546
|
await installDaemon({ host, hostLabel: parsed.hostLabel || host, repoSources });
|
|
541
|
-
console.log(`Updated the daemon on ${host}
|
|
547
|
+
console.log(`Updated the daemon on ${describeHost(host)}, and restarted it.`);
|
|
548
|
+
console.log(`Its ${repoSources.length} key source(s) were left as they are, along with the keys and`);
|
|
549
|
+
console.log(`signers it has already accepted. Only the daemon itself changed:`);
|
|
550
|
+
for (let repoURL of repoSources) {
|
|
551
|
+
console.log(` ${repoURL}`);
|
|
552
|
+
}
|
|
542
553
|
}
|
|
543
554
|
|
|
544
555
|
async function listSources(host: string) {
|
|
545
556
|
let remoteConfig = await readRemoteConfig(host);
|
|
546
557
|
if (!remoteConfig.repoSources.length) {
|
|
547
|
-
console.log(`${host} has no key sources. root's authorized_keys is left exactly as it is.`);
|
|
558
|
+
console.log(`${describeHost(host)} has no key sources. root's authorized_keys is left exactly as it is.`);
|
|
548
559
|
return;
|
|
549
560
|
}
|
|
550
|
-
console.log(`${host} lets root log in with the keys from ${remoteConfig.repoSources.length} repo(s):`);
|
|
561
|
+
console.log(`${describeHost(host)} lets root log in with the keys from ${remoteConfig.repoSources.length} repo(s):`);
|
|
551
562
|
let merged = new Set<string>();
|
|
552
563
|
for (let repoURL of remoteConfig.repoSources) {
|
|
553
564
|
let keys = await readRemoteSourceKeys({ host, repoURL });
|
|
@@ -578,11 +589,16 @@ function parseArgs(argv: string[]) {
|
|
|
578
589
|
throw new Error(`Expected one of ${VERBS.join(", ")}, was ${verbs.join(" and ")}\n${USAGE}`);
|
|
579
590
|
}
|
|
580
591
|
let verb = verbs[0];
|
|
581
|
-
let
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
592
|
+
let positional = argv.filter(arg => arg !== verb);
|
|
593
|
+
// A host, when there is one, comes first and is a bare name or address. Everything else that
|
|
594
|
+
// can appear here is a path or a repo url, and those all carry a slash, which is what tells
|
|
595
|
+
// them apart. So no host at all means this machine.
|
|
596
|
+
let host = THIS_MACHINE;
|
|
597
|
+
if (positional.length && !/[\/~\\]/.test(positional[0])) {
|
|
598
|
+
host = positional[0];
|
|
599
|
+
positional = positional.slice(1);
|
|
600
|
+
}
|
|
601
|
+
return { verb, host, rest: positional };
|
|
586
602
|
}
|
|
587
603
|
|
|
588
604
|
export async function main() {
|
|
@@ -4,17 +4,20 @@ import path from "path";
|
|
|
4
4
|
import { runPromise } from "socket-function/src/runPromise";
|
|
5
5
|
import { keyFingerprint, normalizeKeys } from "./authorizedKeys";
|
|
6
6
|
import { revokeRepoURL } from "./revokeSource";
|
|
7
|
+
import { signRepo } from "../signedFiles/signFiles";
|
|
7
8
|
import { readRepoKeys } from "./authorizedKeys";
|
|
8
9
|
import { expandHome } from "../helpers/paths";
|
|
9
10
|
import { spawnPromise } from "../helpers/spawn";
|
|
10
11
|
|
|
11
12
|
const UNREVOKES_DIR = "unrevoked";
|
|
12
13
|
const REVOCATIONS_DIR = "revocations";
|
|
13
|
-
const
|
|
14
|
+
const GIT_KEYWORD = "git";
|
|
15
|
+
const COMMIT_MESSAGE = "unrevoke keys";
|
|
16
|
+
const USAGE = `Usage: yarn unrevoke [keys-repo] [${GIT_KEYWORD}]
|
|
14
17
|
|
|
15
18
|
Run this in a keys repo, or name one. It reads that repo's revoke repo and writes one unrevoke file
|
|
16
19
|
naming every revocation in it, so the keys are accepted again once each machine's hour long wait
|
|
17
|
-
passes.
|
|
20
|
+
passes. It signs the result, and with ${GIT_KEYWORD} it commits and pushes it too.
|
|
18
21
|
|
|
19
22
|
Keys that were revoked should normally be deleted from the repo instead. Unrevoking only matters
|
|
20
23
|
for a key you still want.`;
|
|
@@ -92,17 +95,24 @@ export async function revokedKeysInRepo(config: { repoPath: string; sourceURL: s
|
|
|
92
95
|
repo that actually holds keys and has an origin to clone from. */
|
|
93
96
|
async function resolveKeysRepo(named: string | undefined) {
|
|
94
97
|
let cwd = named && expandHome(named) || undefined;
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
+
// spawnPromise rather than runPromise, because these two are read for their value. runPromise
|
|
99
|
+
// returns stdout and stderr joined, so one git warning ends up glued to the front of the path.
|
|
100
|
+
let topLevel = await spawnPromise({ command: "git", args: ["rev-parse", "--show-toplevel"], cwd });
|
|
101
|
+
let repoPath = topLevel.stdout.trim();
|
|
102
|
+
if (topLevel.status !== 0 || !repoPath) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`Expected ${named || "the current directory"} to be inside a git repo, it is not.\n`
|
|
105
|
+
+ `${(topLevel.stdout + topLevel.stderr).trim()}\n${USAGE}`
|
|
106
|
+
);
|
|
98
107
|
}
|
|
99
108
|
try {
|
|
100
109
|
await readRepoKeys(repoPath);
|
|
101
110
|
} catch (e) {
|
|
102
111
|
throw new Error(`Expected ${repoPath} to be a keys repo, it holds no keys.\n${e}\n${USAGE}`);
|
|
103
112
|
}
|
|
104
|
-
let
|
|
105
|
-
|
|
113
|
+
let origin = await spawnPromise({ command: "git", args: ["remote", "get-url", "origin"], cwd: repoPath });
|
|
114
|
+
let originURL = origin.stdout.trim();
|
|
115
|
+
if (origin.status !== 0 || !originURL) {
|
|
106
116
|
throw new Error(`Expected ${repoPath} to have an origin remote, it has none.\n${USAGE}`);
|
|
107
117
|
}
|
|
108
118
|
return { repoPath, originURL };
|
|
@@ -110,10 +120,12 @@ async function resolveKeysRepo(named: string | undefined) {
|
|
|
110
120
|
|
|
111
121
|
export async function main() {
|
|
112
122
|
let argv = process.argv.slice(2);
|
|
113
|
-
|
|
114
|
-
|
|
123
|
+
let pushToGit = argv.includes(GIT_KEYWORD);
|
|
124
|
+
let positional = argv.filter(arg => arg !== GIT_KEYWORD);
|
|
125
|
+
if (positional.length > 1) {
|
|
126
|
+
throw new Error(`Expected at most a keys repo, was ${positional.length} argument(s)\n${USAGE}`);
|
|
115
127
|
}
|
|
116
|
-
let { repoPath, originURL } = await resolveKeysRepo(
|
|
128
|
+
let { repoPath, originURL } = await resolveKeysRepo(positional[0]);
|
|
117
129
|
|
|
118
130
|
let revocations = await readRemoteRevocations({ sourceURL: originURL });
|
|
119
131
|
if (!revocations.length) {
|
|
@@ -144,6 +156,20 @@ export async function main() {
|
|
|
144
156
|
for (let revocation of revocations) {
|
|
145
157
|
console.log(` ${revocation.fingerprint} revoked by ${revocation.revokedBy || "?"} from ${revocation.attempt?.ip || "?"}`);
|
|
146
158
|
}
|
|
147
|
-
|
|
148
|
-
|
|
159
|
+
|
|
160
|
+
// Signed here rather than left as a step to remember. An unrevoke nobody signed does nothing at
|
|
161
|
+
// all, and the repo would sit there looking done while every machine ignored it.
|
|
162
|
+
await signRepo({ repoPath });
|
|
163
|
+
|
|
164
|
+
if (!pushToGit) {
|
|
165
|
+
console.log(`\nCommit and push it, and each machine will wait an hour after seeing it before`);
|
|
166
|
+
console.log(`those keys work again:`);
|
|
167
|
+
console.log(`\`\`\`\ngit add -A\ngit commit -m "${COMMIT_MESSAGE}"\ngit push\n\`\`\``);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
await runPromise(`git add -A`, { cwd: repoPath });
|
|
171
|
+
await runPromise(`git commit -m "${COMMIT_MESSAGE}"`, { cwd: repoPath });
|
|
172
|
+
await runPromise(`git push`, { cwd: repoPath });
|
|
173
|
+
console.log(`\nCommitted and pushed. Each machine waits an hour after seeing it before those`);
|
|
174
|
+
console.log(`keys work again.`);
|
|
149
175
|
}
|
|
@@ -8,28 +8,39 @@ const MAX_ERROR_BODY_LENGTH = 500;
|
|
|
8
8
|
// /etc is root owned, so fall back to sudo whenever the SSH user is not root.
|
|
9
9
|
export const SUDO_PREAMBLE = `SUDO=""; if [ "$(id -u)" -ne 0 ]; then SUDO="sudo -n"; fi`;
|
|
10
10
|
|
|
11
|
+
/** No host means this machine, so the very same script runs with nothing in front of it. Local and
|
|
12
|
+
remote then do exactly the same thing, rather than being two pieces of code that have to be kept
|
|
13
|
+
saying the same thing. */
|
|
14
|
+
export const THIS_MACHINE = "";
|
|
15
|
+
|
|
16
|
+
export function describeHost(host: string) {
|
|
17
|
+
return host || "this machine";
|
|
18
|
+
}
|
|
19
|
+
|
|
11
20
|
/** The host string is handed to ssh untouched. Users, keys and ports belong in the caller's ssh
|
|
12
21
|
config, so BatchMode makes a missing setup fail immediately instead of prompting. */
|
|
13
22
|
export async function runOverSSH(config: { host: string; script: string; input?: string; allowFailure?: boolean }) {
|
|
14
23
|
let { host, script, input, allowFailure } = config;
|
|
15
24
|
let result = await spawnPromise({
|
|
16
|
-
command: "ssh",
|
|
17
|
-
args: [
|
|
25
|
+
command: host && "ssh" || "sh",
|
|
26
|
+
args: host && [
|
|
18
27
|
"-o", "BatchMode=yes",
|
|
19
28
|
"-o", `ConnectTimeout=${SSH_CONNECT_TIMEOUT}`,
|
|
20
29
|
host,
|
|
21
30
|
script,
|
|
22
|
-
],
|
|
31
|
+
] || ["-c", script],
|
|
23
32
|
input,
|
|
24
33
|
inheritStderr: !allowFailure,
|
|
25
34
|
});
|
|
26
35
|
if (result.error) {
|
|
27
|
-
throw new Error(`Expected
|
|
36
|
+
throw new Error(`Expected to run against ${describeHost(host)}, failed with ${result.error.message}`);
|
|
28
37
|
}
|
|
29
38
|
if (result.status !== 0 && !allowFailure) {
|
|
39
|
+
let advice = host
|
|
40
|
+
&& ` Non-interactive ssh access to ${host} has to work on its own - fix it in your ssh config.`
|
|
41
|
+
|| "";
|
|
30
42
|
throw new Error(
|
|
31
|
-
`Expected
|
|
32
|
-
+ ` Non-interactive ssh access to ${host} has to work on its own - fix it in your ssh config.\n`
|
|
43
|
+
`Expected the script on ${describeHost(host)} to exit 0, was ${result.status}.${advice}\n`
|
|
33
44
|
+ `${(result.stdout + result.stderr).trim().slice(0, MAX_ERROR_BODY_LENGTH)}`
|
|
34
45
|
);
|
|
35
46
|
}
|
|
@@ -72,8 +72,25 @@ async function readWebhookFile(filePath: string) {
|
|
|
72
72
|
return parseWebhookFile({ contents, sourceName: filePath });
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
/** When the message was sent, in the sending machine's own time zone. Discord shows when it
|
|
76
|
+
received something, which is not the same thing when a machine has been offline or a send has
|
|
77
|
+
been retried, and the zone matters when the machines are not all in one place. */
|
|
78
|
+
export function messageTimestamp(now: Date) {
|
|
79
|
+
let date = now.toLocaleDateString("en-CA");
|
|
80
|
+
let time = now.toLocaleTimeString("en-US", {
|
|
81
|
+
hour: "2-digit",
|
|
82
|
+
minute: "2-digit",
|
|
83
|
+
second: "2-digit",
|
|
84
|
+
hour12: true,
|
|
85
|
+
timeZoneName: "short",
|
|
86
|
+
});
|
|
87
|
+
return `${date} ${time}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
75
90
|
async function postToWebhook(webhookURL: string, message: string) {
|
|
76
|
-
|
|
91
|
+
// Stamped once rather than per attempt, so a retry says when the thing happened rather than
|
|
92
|
+
// when we last managed to get it out.
|
|
93
|
+
let content = `\`${messageTimestamp(new Date())}\` ${message}`;
|
|
77
94
|
if (content.length > DISCORD_MESSAGE_LIMIT) {
|
|
78
95
|
content = content.slice(0, DISCORD_MESSAGE_LIMIT - 3) + "...";
|
|
79
96
|
}
|
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule && mod.default) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true , configurable: true});
|
|
6
|
-
//exports.stopDiscordNotifications = exports.sendDiscordNotification = exports.configureDiscordNotifications = exports.sendToWebhookURL = exports.formatWebhookFile = exports.redactWebhookURL = exports.parseWebhookFile = exports.DEFAULT_WEBHOOK_FILE_PATH = void 0;
|
|
6
|
+
//exports.stopDiscordNotifications = exports.sendDiscordNotification = exports.configureDiscordNotifications = exports.sendToWebhookURL = exports.messageTimestamp = exports.formatWebhookFile = exports.redactWebhookURL = exports.parseWebhookFile = exports.DEFAULT_WEBHOOK_FILE_PATH = void 0;
|
|
7
7
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
8
|
// PORTED CODE: security/authorizedKeys/daemon/portsecureDaemon.js embeds a hand port of this file in plain JS, so the
|
|
9
9
|
// daemon can run with no dependencies. The two are expected to behave identically - if you change
|
|
@@ -67,8 +67,25 @@ async function readWebhookFile(filePath) {
|
|
|
67
67
|
}
|
|
68
68
|
return parseWebhookFile({ contents, sourceName: filePath });
|
|
69
69
|
}
|
|
70
|
+
/** When the message was sent, in the sending machine's own time zone. Discord shows when it
|
|
71
|
+
received something, which is not the same thing when a machine has been offline or a send has
|
|
72
|
+
been retried, and the zone matters when the machines are not all in one place. */
|
|
73
|
+
function messageTimestamp(now) {
|
|
74
|
+
let date = now.toLocaleDateString("en-CA");
|
|
75
|
+
let time = now.toLocaleTimeString("en-US", {
|
|
76
|
+
hour: "2-digit",
|
|
77
|
+
minute: "2-digit",
|
|
78
|
+
second: "2-digit",
|
|
79
|
+
hour12: true,
|
|
80
|
+
timeZoneName: "short",
|
|
81
|
+
});
|
|
82
|
+
return `${date} ${time}`;
|
|
83
|
+
}
|
|
84
|
+
exports.messageTimestamp = messageTimestamp;
|
|
70
85
|
async function postToWebhook(webhookURL, message) {
|
|
71
|
-
|
|
86
|
+
// Stamped once rather than per attempt, so a retry says when the thing happened rather than
|
|
87
|
+
// when we last managed to get it out.
|
|
88
|
+
let content = `\`${messageTimestamp(new Date())}\` ${message}`;
|
|
72
89
|
if (content.length > DISCORD_MESSAGE_LIMIT) {
|
|
73
90
|
content = content.slice(0, DISCORD_MESSAGE_LIMIT - 3) + "...";
|
|
74
91
|
}
|
|
@@ -176,5 +193,5 @@ function stopDiscordNotifications() {
|
|
|
176
193
|
notificationState = undefined;
|
|
177
194
|
}
|
|
178
195
|
exports.stopDiscordNotifications = stopDiscordNotifications;
|
|
179
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGlzY29yZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImRpc2NvcmQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUEsMkRBQTZCO0FBRTdCLHNIQUFzSDtBQUN0SCxrR0FBa0c7QUFDbEcsOENBQThDO0FBRWpDLFFBQUEseUJBQXlCLEdBQUcsaUNBQWlDLENBQUM7QUFDM0UsTUFBTSxzQkFBc0IsR0FBRyxDQUFDLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQztBQUM3QyxNQUFNLHNCQUFzQixHQUFHO0lBQzNCLG1DQUFtQztJQUNuQyxzQ0FBc0M7SUFDdEMsMENBQTBDO0NBQzdDLENBQUM7QUFDRixNQUFNLHFCQUFxQixHQUFHLElBQUksQ0FBQztBQUNuQyxNQUFNLHFCQUFxQixHQUFHLEdBQUcsQ0FBQztBQUNsQyxNQUFNLGlCQUFpQixHQUFHLENBQUMsQ0FBQztBQUM1QixNQUFNLHNCQUFzQixHQUFHLENBQUMsQ0FBQztBQUNqQyxNQUFNLHVCQUF1QixHQUFHLENBQUMsR0FBRyxJQUFJLENBQUM7QUFFekMsSUFBSSxpQkFJUyxDQUFDO0FBRWQsc0ZBQXNGO0FBQ3RGLDRDQUE0QztBQUM1QyxJQUFJLFNBQVMsR0FBcUIsT0FBTyxDQUFDLE9BQU8sRUFBRSxDQUFDO0FBRXBEO3NDQUNzQztBQUN0QyxTQUFnQixnQkFBZ0IsQ0FBQyxNQUFnRDtJQUM3RSxJQUFJLEVBQUUsUUFBUSxFQUFFLFVBQVUsRUFBRSxHQUFHLE1BQU0sQ0FBQztJQUN0QyxJQUFJLEtBQUssR0FBRyxRQUFRLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztJQUN4RyxJQUFJLFVBQVUsR0FBRyxL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QUMsS0FBSyxFQUFFLENBQUM7UUFDVCxNQUFNLElBQUksS0FBSyxDQUFDLCtGQUErRixPQUFPLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxxQkFBcUIsQ0FBQyxFQUFFLENBQUMsQ0FBQztJQUM5SixDQUFDO0lBQ0QsTUFBTSxTQUFTLENBQUMsS0FBSyxDQUFDLFVBQVUsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUMvQyxDQUFDO0FBTkQsMERBTUM7QUFFRCxTQUFnQix3QkFBd0I7SUFDcEMsSUFBSSxDQUFDLGlCQUFpQixFQUFFLENBQUM7UUFDckIsT0FBTztJQUNYLENBQUM7SUFDRCxhQUFhLENBQUMsaUJBQWlCLENBQUMsVUFBVSxDQUFDLENBQUM7SUFDNUMsaUJBQWlCLEdBQUcsU0FBUyxDQUFDO0FBQ2xDLENBQUM7QUFORCw0REFNQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBmcyBmcm9tIFwiZnMvcHJvbWlzZXNcIjtcblxuLy8gUE9SVEVEIENPREU6IHNlY3VyaXR5L2F1dGhvcml6ZWRLZXlzL2RhZW1vbi9wb3J0c2VjdXJlRGFlbW9uLmpzIGVtYmVkcyBhIGhhbmQgcG9ydCBvZiB0aGlzIGZpbGUgaW4gcGxhaW4gSlMsIHNvIHRoZVxuLy8gZGFlbW9uIGNhbiBydW4gd2l0aCBubyBkZXBlbmRlbmNpZXMuIFRoZSB0d28gYXJlIGV4cGVjdGVkIHRvIGJlaGF2ZSBpZGVudGljYWxseSAtIGlmIHlvdSBjaGFuZ2Vcbi8vIG9uZSwgbWFrZSB0aGUgbWF0Y2hpbmcgY2hhbmdlIGluIHRoZSBvdGhlci5cblxuZXhwb3J0IGNvbnN0IERFRkFVTFRfV0VCSE9PS19GSUxFX1BBVEggPSBcIi9ldGMvcG9ydHNlY3VyZS9kaXNjb3JkLXdlYmhvb2tcIjtcbmNvbnN0IERFRkFVTFRfQ0hFQ0tfSU5URVJWQUwgPSA1ICogNjAgKiAxMDAwO1xuY29uc3QgVkFMSURfV0VCSE9PS19QUkVGSVhFUyA9IFtcbiAgICBcImh0dHBzOi8vZGlzY29yZC5jb20vYXBpL3dlYmhvb2tzL1wiLFxuICAgIFwiaHR0cHM6Ly9kaXNjb3JkYXBwLmNvbS9hcGkvd2ViaG9va3MvXCIsXG4gICAgXCJodHRwczovL2NhbmFyeS5kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvXCIsXG5dO1xuY29uc3QgRElTQ09SRF9NRVNTQUdFX0xJTUlUID0gMjAwMDtcbmNvbnN0IE1BWF9FUlJPUl9CT0RZX0xFTkdUSCA9IDUwMDtcbmNvbnN0IE1BWF9TRU5EX0FUVEVNUFRTID0gMztcbmNvbnN0IFJFREFDVEVEX1RPS0VOX1ZJU0lCTEUgPSA4O1xuY29uc3QgREVGQVVMVF9SQVRFX0xJTUlUX1dBSVQgPSAyICogMTAwMDtcblxubGV0IG5vdGlmaWNhdGlvblN0YXRlOiB7XG4gICAgZmlsZVBhdGg6IHN0cmluZztcbiAgICB3ZWJob29rVVJMOiBzdHJpbmc7XG4gICAgY2hlY2tUaW1lcjogTm9kZUpTLlRpbWVvdXQ7XG59IHwgdW5kZWZpbmVkO1xuXG4vLyBEaXNjb3JkIHdlYmhvb2tzIGFyZSByYXRlIGxpbWl0ZWQgKHJvdWdobHkgNSByZXF1ZXN0cyBwZXIgMiBzZWNvbmRzKSwgc28gZXZlcnkgc2VuZFxuLy8gZ29lcyB0aHJvdWdoIG9uZSBjaGFpbiBpbnN0ZWFkIG9mIHJhY2luZy5cbmxldCBzZW5kQ2hhaW46IFByb21pc2U8dW5rbm93bj4gPSBQcm9taXNlLnJlc29sdmUoKTtcblxuLyoqIFB1bGxzIHRoZSB3ZWJob29rIFVSTCBvdXQgb2Ygd2ViaG9vayBmaWxlIGNvbnRlbnRzLiBgc291cmNlTmFtZWAgb25seSBhcHBlYXJzIGluIGVycm9ycywgc29cbiAgICBjYWxsZXJzIGNhbiBuYW1lIGEgcmVtb3RlIHBhdGguICovXG5leHBvcnQgZnVuY3Rpb24gcGFyc2VXZWJob29rRmlsZShjb25maWc6IHsgY29udGVudHM6IHN0cmluZzsgc291cmNlTmFtZTogc3RyaW5nIH0pIHtcbiAgICBsZXQgeyBjb250ZW50cywgc291cmNlTmFtZSB9ID0gY29uZmlnO1xuICAgIGxldCBsaW5lcyA9IGNvbnRlbnRzLnNwbGl0KFwiXFxuXCIpLm1hcChsaW5lID0+IGxpbmUudHJpbSgpKS5maWx0ZXIobGluZSA9PiBsaW5lICYmICFsaW5lLnN0YXJ0c1dpdGgoXCIjXCIpKTtcbiAgICBsZXQgd2ViaG9va1VSTCA9IGxpbmVzWzBdO1xuICAgIGlmICghd2ViaG9va1VSTCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIGEgRGlzY29yZCB3ZWJob29rIFVSTCBpbiAke3NvdXJjZU5hbWV9LCB0aGUgZmlsZSBoYXMgbm8gdXNhYmxlIGxpbmVzYCk7XG4gICAgfVxuICAgIGlmICghVkFMSURfV0VCSE9PS19QUkVGSVhFUy5zb21lKHByZWZpeCA9PiB3ZWJob29rVVJMLnN0YXJ0c1dpdGgocHJlZml4KSkpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgYEV4cGVjdGVkIGEgRGlzY29yZCB3ZWJob29rIFVSTCBzdGFydGluZyB3aXRoIG9uZSBvZiAke1ZBTElEX1dFQkhPT0tfUFJFRklYRVMuam9pbihcIiwgXCIpfSwgYFxuICAgICAgICAgICAgKyBgd2FzICR7d2ViaG9va1VSTC5zbGljZSgwLCBNQVhfRVJST1JfQk9EWV9MRU5HVEgpfSAoaW4gJHtzb3VyY2VOYW1lfSlgXG4gICAgICAgICk7XG4gICAgfVxuICAgIHJldHVybiB3ZWJob29rVVJMO1xufVxuXG4vKiogS2VlcHMgdGhlIGlkIGFuZCBib3RoIGVuZHMgb2YgdGhlIHRva2VuLCBzbyBhIHJlYWRlciBjYW4gY29uZmlybSB3aGljaCB3ZWJob29rIHJlcGxhY2VkIHRoZWlyc1xuICAgIHdpdGhvdXQgcmVjZWl2aW5nIG9uZSB0aGV5IGNvdWxkIHBvc3QgdG8uICovXG5leHBvcnQgZnVuY3Rpb24gcmVkYWN0V2ViaG9va1VSTCh3ZWJob29rVVJMOiBzdHJpbmcpIHtcbiAgICBsZXQgc2VwYXJhdG9ySW5kZXggPSB3ZWJob29rVVJMLmxhc3RJbmRleE9mKFwiL1wiKTtcbiAgICBsZXQgYmFzZSA9IHdlYmhvb2tVUkwuc2xpY2UoMCwgc2VwYXJhdG9ySW5kZXggKyAxKTtcbiAgICBsZXQgdG9rZW4gPSB3ZWJob29rVVJMLnNsaWNlKHNlcGFyYXRvckluZGV4ICsgMSk7XG4gICAgaWYgKHRva2VuLmxlbmd0aCA8PSBSRURBQ1RFRF9UT0tFTl9WSVNJQkxFICogMikge1xuICAgICAgICAvLyBUb28gc2hvcnQgdG8gc2hvdyBib3RoIGVuZHMgd2l0aG91dCBnaXZpbmcgYXdheSB0aGUgd2hvbGUgdG9rZW4uXG4gICAgICAgIHJldHVybiBgJHtiYXNlfSR7dG9rZW4uc2xpY2UoMCwgUkVEQUNURURfVE9LRU5fVklTSUJMRSl9Li4uYDtcbiAgICB9XG4gICAgcmV0dXJuIGAke2Jhc2V9JHt0b2tlbi5zbGljZSgwLCBSRURBQ1RFRF9UT0tFTl9WSVNJQkxFKX0uLi4ke3Rva2VuLnNsaWNlKC1SRURBQ1RFRF9UT0tFTl9WSVNJQkxFKX1gO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZm9ybWF0V2ViaG9va0ZpbGUod2ViaG9va1VSTDogc3RyaW5nKSB7XG4gICAgcmV0dXJuIGAjIHBvcnRzZWN1cmUgRGlzY29yZCB3ZWJob29rLiBGaXJzdCBub24tY29tbWVudCBsaW5lIGlzIHVzZWQuXFxuJHt3ZWJob29rVVJMfVxcbmA7XG59XG5cbmFzeW5jIGZ1bmN0aW9uIHJlYWRXZWJob29rRmlsZShmaWxlUGF0aDogc3RyaW5nKSB7XG4gICAgbGV0IGNvbnRlbnRzO1xuICAgIHRyeSB7XG4gICAgICAgIGNvbnRlbnRzID0gYXdhaXQgZnMucmVhZEZpbGUoZmlsZVBhdGgsIFwidXRmOFwiKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgRXhwZWN0ZWQgYSByZWFkYWJsZSBEaXNjb3JkIHdlYmhvb2sgZmlsZSBhdCAke2ZpbGVQYXRofSwgJHtlfWApO1xuICAgIH1cbiAgICByZXR1cm4gcGFyc2VXZWJob29rRmlsZSh7IGNvbnRlbnRzLCBzb3VyY2VOYW1lOiBmaWxlUGF0aCB9KTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gcG9zdFRvV2ViaG9vayh3ZWJob29rVVJMOiBzdHJpbmcsIG1lc3NhZ2U6IHN0cmluZykge1xuICAgIGxldCBjb250ZW50ID0gbWVzc2FnZTtcbiAgICBpZiAoY29udGVudC5sZW5ndGggPiBESVNDT1JEX01FU1NBR0VfTElNSVQpIHtcbiAgICAgICAgY29udGVudCA9IGNvbnRlbnQuc2xpY2UoMCwgRElTQ09SRF9NRVNTQUdFX0xJTUlUIC0gMykgKyBcIi4uLlwiO1xuICAgIH1cbiAgICBmb3IgKGxldCBhdHRlbXB0ID0gMTsgYXR0ZW1wdCA8PSBNQVhfU0VORF9BVFRFTVBUUzsgYXR0ZW1wdCsrKSB7XG4gICAgICAgIGxldCByZXNwb25zZSA9IGF3YWl0IGZldGNoKHdlYmhvb2tVUkwsIHtcbiAgICAgICAgICAgIG1ldGhvZDogXCJQT1NUXCIsXG4gICAgICAgICAgICBoZWFkZXJzOiB7IFwiQ29udGVudC1UeXBlXCI6IFwiYXBwbGljYXRpb24vanNvblwiIH0sXG4gICAgICAgICAgICBib2R5OiBKU09OLnN0cmluZ2lmeSh7IGNvbnRlbnQgfSksXG4gICAgICAgIH0pO1xuICAgICAgICBpZiAocmVzcG9uc2Uub2spIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBsZXQgYm9keSA9IChhd2FpdCByZXNwb25zZS50ZXh0KCkpLnNsaWNlKDAsIE1BWF9FUlJPUl9CT0RZX0xFTkdUSCk7XG4gICAgICAgIGlmIChyZXNwb25zZS5zdGF0dXMgPT09IDQyOSAmJiBhdHRlbXB0IDwgTUFYX1NFTkRfQVRURU1QVFMpIHtcbiAgICAgICAgICAgIGxldCByZXRyeUFmdGVyID0gTnVtYmVyKHJlc3BvbnNlLmhlYWRlcnMuZ2V0KFwicmV0cnktYWZ0ZXJcIikpO1xuICAgICAgICAgICAgbGV0IHdhaXRUaW1lID0gcmV0cnlBZnRlciAmJiByZXRyeUFmdGVyICogMTAwMCB8fCBERUZBVUxUX1JBVEVfTElNSVRfV0FJVDtcbiAgICAgICAgICAgIGF3YWl0IG5ldyBQcm9taXNlKHJlc29sdmUgPT4gc2V0VGltZW91dChyZXNvbHZlLCB3YWl0VGltZSkpO1xuICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgYEV4cGVjdGVkIGEgMnh4IHJlc3BvbnNlIGZyb20gdGhlIERpc2NvcmQgd2ViaG9vaywgd2FzICR7cmVzcG9uc2Uuc3RhdHVzfSAke3Jlc3BvbnNlLnN0YXR1c1RleHR9LCBib2R5ICR7Ym9keX1gXG4gICAgICAgICk7XG4gICAgfVxufVxuXG5mdW5jdGlvbiBxdWV1ZVNlbmQod2ViaG9va1VSTDogc3RyaW5nLCBtZXNzYWdlOiBzdHJpbmcpIHtcbiAgICBsZXQgc2VuZCA9ICgpID0+IHBvc3RUb1dlYmhvb2sod2ViaG9va1VSTCwgbWVzc2FnZSk7XG4gICAgbGV0IHJlc3VsdCA9IHNlbmRDaGFpbi50aGVuKHNlbmQsIHNlbmQpO1xuICAgIHNlbmRDaGFpbiA9IHJlc3VsdC5jYXRjaCgoKSA9PiB1bmRlZmluZWQpO1xuICAgIHJldHVybiByZXN1bHQ7XG59XG5cbi8qKiBTZW5kcyB0byBhbiBleHBsaWNpdCB3ZWJob29rIFVSTCwgZm9yIHRvb2xpbmcgdGhhdCBhY3RzIG9uIGEgd2ViaG9vayBiZWZvcmUgKG9yIGluc3RlYWQgb2YpXG4gICAgY29uZmlndXJlRGlzY29yZE5vdGlmaWNhdGlvbnMgLSBzZXR1cCwgbWlncmF0aW9ucywgY29ubmVjdGl2aXR5IGNoZWNrcy4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzZW5kVG9XZWJob29rVVJMKGNvbmZpZzogeyB3ZWJob29rVVJMOiBzdHJpbmc7IG1lc3NhZ2U6IHN0cmluZyB9KSB7XG4gICAgcmV0dXJuIHF1ZXVlU2VuZChjb25maWcud2ViaG9va1VSTCwgY29uZmlnLm1lc3NhZ2UpO1xufVxuXG5hc3luYyBmdW5jdGlvbiBjaGVja1dlYmhvb2tGaWxlQ2hhbmdlZCgpIHtcbiAgICBsZXQgc3RhdGUgPSBub3RpZmljYXRpb25TdGF0ZTtcbiAgICBpZiAoIXN0YXRlKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgbGV0IG5ld1dlYmhvb2tVUkw7XG4gICAgdHJ5IHtcbiAgICAgICAgbmV3V2ViaG9va1VSTCA9IGF3YWl0IHJlYWRXZWJob29rRmlsZShzdGF0ZS5maWxlUGF0aCk7XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgICBjb25zb2xlLmVycm9yKGBwb3J0c2VjdXJlOiBEaXNjb3JkIHdlYmhvb2sgZmlsZSBpcyBubyBsb25nZXIgcmVhZGFibGUsIHN0aWxsIHVzaW5nIHRoZSBsb2FkZWQgd2ViaG9vay4gJHtlfWApO1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmIChuZXdXZWJob29rVVJMID09PSBzdGF0ZS53ZWJob29rVVJMKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgLy8gV2FybiB0aGUgb2xkIGNoYW5uZWwgZmlyc3QsIHdpdGggdGhlIG5ldyB3ZWJob29rIHJlZGFjdGVkLCBzbyBhIHN0b2xlbiBvbGQgd2ViaG9vayBkb2VzIG5vdFxuICAgIC8vIGhhbmQgdGhlIGF0dGFja2VyIGEgdXNhYmxlIG5ldyBvbmUuXG4gICAgdHJ5IHtcbiAgICAgICAgYXdhaXQgcXVldWVTZW5kKFxuICAgICAgICAgICAgc3RhdGUud2ViaG9va1VSTCxcbiAgICAgICAgICAgIGAqKnBvcnRzZWN1cmUqKjogdGhlIERpc2NvcmQgd2ViaG9vayBpbiBcXGAke3N0YXRlLmZpbGVQYXRofVxcYCBjaGFuZ2VkIHRvYFxuICAgICAgICAgICAgKyBgIFxcYCR7cmVkYWN0V2ViaG9va1VSTChuZXdXZWJob29rVVJMKX1cXGAuYFxuICAgICAgICAgICAgKyBgIE5vdGlmaWNhdGlvbnMgYXJlIG1vdmluZyB0byB0aGUgbmV3IHdlYmhvb2sgYW5kIHRoaXMgY2hhbm5lbCB3aWxsIHN0b3AgcmVjZWl2aW5nIHRoZW0uYFxuICAgICAgICApO1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgY29uc29sZS5lcnJvcihgcG9ydHNlY3VyZTogZmFpbGVkIHRvIHdhcm4gdGhlIG9sZCBEaXNjb3JkIHdlYmhvb2sgYWJvdXQgdGhlIGNoYW5nZS4gJHtlfWApO1xuICAgIH1cbiAgICBzdGF0ZS53ZWJob29rVVJMID0gbmV3V2ViaG9va1VSTDtcbn1cblxuLyoqIE11c3QgYmUgY2FsbGVkIG9uY2Ugb24gc3RhcnR1cCwgYmVmb3JlIGFueSBub3RpZmljYXRpb24gaXMgc2VudC4gQWJvcnRzIHRoZSBwcm9jZXNzIGlmIHRoZVxuICAgIHdlYmhvb2sgZmlsZSBpcyBtaXNzaW5nIG9yIGludmFsaWQsIHRoZW4gcmUtY2hlY2tzIHRoZSBmaWxlIG9uIGFuIGludGVydmFsIGFuZCB3YXJucyB0aGUgb2xkXG4gICAgd2ViaG9vayB3aGVuZXZlciBpdCBjaGFuZ2VzLiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNvbmZpZ3VyZURpc2NvcmROb3RpZmljYXRpb25zKGNvbmZpZz86IHtcbiAgICBmaWxlUGF0aD86IHN0cmluZztcbiAgICBjaGVja0ludGVydmFsPzogbnVtYmVyO1xufSkge1xuICAgIGlmIChub3RpZmljYXRpb25TdGF0ZSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIGNvbmZpZ3VyZURpc2NvcmROb3RpZmljYXRpb25zIHRvIGJlIGNhbGxlZCBvbmNlLCB3YXMgY2FsbGVkIGFnYWluIChhbHJlYWR5IHVzaW5nICR7bm90aWZpY2F0aW9uU3RhdGUuZmlsZVBhdGh9KWApO1xuICAgIH1cbiAgICBsZXQgZmlsZVBhdGggPSBjb25maWc/LmZpbGVQYXRoIHx8IERFRkFVTFRfV0VCSE9PS19GSUxFX1BBVEg7XG4gICAgbGV0IGNoZWNrSW50ZXJ2YWwgPSBjb25maWc/LmNoZWNrSW50ZXJ2YWwgfHwgREVGQVVMVF9DSEVDS19JTlRFUlZBTDtcbiAgICBsZXQgd2ViaG9va1VSTDtcbiAgICB0cnkge1xuICAgICAgICB3ZWJob29rVVJMID0gYXdhaXQgcmVhZFdlYmhvb2tGaWxlKGZpbGVQYXRoKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIGNvbnNvbGUuZXJyb3IoYHBvcnRzZWN1cmU6IHJlZnVzaW5nIHRvIHN0YXJ0IHdpdGhvdXQgYSB2YWxpZCBEaXNjb3JkIHdlYmhvb2sgZmlsZS5cXG4ke2V9YCk7XG4gICAgICAgIHByb2Nlc3MuZXhpdCgxKTtcbiAgICB9XG4gICAgbGV0IGNoZWNrVGltZXIgPSBzZXRJbnRlcnZhbCgoKSA9PiB7XG4gICAgICAgIGNoZWNrV2ViaG9va0ZpbGVDaGFuZ2VkKCkuY2F0Y2goZSA9PiBjb25zb2xlLmVycm9yKGBwb3J0c2VjdXJlOiBEaXNjb3JkIHdlYmhvb2sgZmlsZSBjaGVjayBmYWlsZWQuICR7ZX1gKSk7XG4gICAgfSwgY2hlY2tJbnRlcnZhbCk7XG4gICAgLy8gVGhlIGNoZWNrIHNob3VsZCBuZXZlciBiZSB0aGUgcmVhc29uIHRoZSBwcm9jZXNzIHN0YXlzIGFsaXZlLlxuICAgIGNoZWNrVGltZXIudW5yZWYoKTtcbiAgICBub3RpZmljYXRpb25TdGF0ZSA9IHsgZmlsZVBhdGgsIHdlYmhvb2tVUkwsIGNoZWNrVGltZXIgfTtcbiAgICByZXR1cm4geyBmaWxlUGF0aCwgY2hlY2tJbnRlcnZhbCB9O1xufVxuXG4vLyBETyBOT1QgYWRkIG5ldyBjYWxscyB0byB0aGlzLiBFdmVyeSBtZXNzYWdlIGdvZXMgdG8gYSByZWFsIERpc2NvcmQgc2VydmVyIHNvbWVvbmUgcmVhZHMsIHNvIGFcbi8vIG5vdGlmaWNhdGlvbiBpcyBvbmx5IGV2ZXIgYWRkZWQgd2hlbiB0aGUgdXNlciBleHBsaWNpdGx5IGFza3MgZm9yIHRoYXQgc3BlY2lmaWMgY2FzZS4gU3RhcnR1cCxcbi8vIHN1Y2Nlc3MsIGVycm9ycywgcmV0cmllcyBhbmQgcmVjb3ZlcmllcyBhbGwgYmVsb25nIGluIGEgbG9nIGluc3RlYWQuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gc2VuZERpc2NvcmROb3RpZmljYXRpb24obWVzc2FnZTogc3RyaW5nKSB7XG4gICAgbGV0IHN0YXRlID0gbm90aWZpY2F0aW9uU3RhdGU7XG4gICAgaWYgKCFzdGF0ZSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIGNvbmZpZ3VyZURpc2NvcmROb3RpZmljYXRpb25zIHRvIGJlIGNhbGxlZCBiZWZvcmUgc2VuZGluZywgd2FzIGNhbGxlZCB3aXRoIG1lc3NhZ2UgJHttZXNzYWdlLnNsaWNlKDAsIE1BWF9FUlJPUl9CT0RZX0xFTkdUSCl9YCk7XG4gICAgfVxuICAgIGF3YWl0IHF1ZXVlU2VuZChzdGF0ZS53ZWJob29rVVJMLCBtZXNzYWdlKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHN0b3BEaXNjb3JkTm90aWZpY2F0aW9ucygpIHtcbiAgICBpZiAoIW5vdGlmaWNhdGlvblN0YXRlKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY2xlYXJJbnRlcnZhbChub3RpZmljYXRpb25TdGF0ZS5jaGVja1RpbWVyKTtcbiAgICBub3RpZmljYXRpb25TdGF0ZSA9IHVuZGVmaW5lZDtcbn1cbiJdfQ==
|
|
180
|
-
/* _JS_SOURCE_HASH = "
|
|
196
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGlzY29yZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImRpc2NvcmQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUEsMkRBQTZCO0FBRTdCLHNIQUFzSDtBQUN0SCxrR0FBa0c7QUFDbEcsOENBQThDO0FBRWpDLFFBQUEseUJBQXlCLEdBQUcsaUNBQWlDLENBQUM7QUFDM0UsTUFBTSxzQkFBc0IsR0FBRyxDQUFDLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQztBQUM3QyxNQUFNLHNCQUFzQixHQUFHO0lBQzNCLG1DQUFtQztJQUNuQyxzQ0FBc0M7SUFDdEMsMENBQTBDO0NBQzdDLENBQUM7QUFDRixNQUFNLHFCQUFxQixHQUFHLElBQUksQ0FBQztBQUNuQyxNQUFNLHFCQUFxQixHQUFHLEdBQUcsQ0FBQztBQUNsQyxNQUFNLGlCQUFpQixHQUFHLENBQUMsQ0FBQztBQUM1QixNQUFNLHNCQUFzQixHQUFHLENBQUMsQ0FBQztBQUNqQyxNQUFNLHVCQUF1QixHQUFHLENBQUMsR0FBRyxJQUFJLENBQUM7QUFFekMsSUFBSSxpQkFJUyxDQUFDO0FBRWQsc0ZBQXNGO0FBQ3RGLDRDQUE0QztBQUM1QyxJQUFJLFNBQVMsR0FBcUIsT0FBTyxDQUFDLE9BQU8sRUFBRSxDQUFDO0FBRXBEO3NDQUNzQztBQUN0QyxTQUFnQixnQkFBZ0IsQ0FBQyxNQUFnRDtJQUM3RSxJQUFJLEVBQUUsUUFBUSxFQUFFLFVBQVUsRUFBRSxHQUFHLE1BQU0sQ0FBQztJQUN0QyxJQUFJLEtBQUssR0FBRyxRQUFRLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztJQUN4RyxJQUFJLFVBQVUsR0FBRyxL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RUFBRSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsa0RBQWtELENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQztJQUMvRyxDQUFDLEVBQUUsYUFBYSxDQUFDLENBQUM7SUFDbEIsZ0VBQWdFO0lBQ2hFLFVBQVUsQ0FBQyxLQUFLLEVBQUUsQ0FBQztJQUNuQixpQkFBaUIsR0FBRyxFQUFFLFFBQVEsRUFBRSxVQUFVLEVBQUUsVUFBVSxFQUFFLENBQUM7SUFDekQsT0FBTyxFQUFFLFFBQVEsRUFBRSxhQUFhLEVBQUUsQ0FBQztBQUN2QyxDQUFDO0FBdkJELHNFQXVCQztBQUVELGdHQUFnRztBQUNoRyxpR0FBaUc7QUFDakcsdUVBQXVFO0FBQ2hFLEtBQUssVUFBVSx1QkFBdUIsQ0FBQyxPQUFlO0lBQ3pELElBQUksS0FBSyxHQUFHLGlCQUFpQixDQUFDO0lBQzlCLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUNULE1BQU0sSUFBSSxLQUFLLENBQUMsK0ZBQStGLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLHFCQUFxQixDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQzlKLENBQUM7SUFDRCxNQUFNLFNBQVMsQ0FBQyxLQUFLLENBQUMsVUFBVSxFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBQy9DLENBQUM7QUFORCwwREFNQztBQUVELFNBQWdCLHdCQUF3QjtJQUNwQyxJQUFJLENBQUMsaUJBQWlCLEVBQUUsQ0FBQztRQUNyQixPQUFPO0lBQ1gsQ0FBQztJQUNELGFBQWEsQ0FBQyxpQkFBaUIsQ0FBQyxVQUFVLENBQUMsQ0FBQztJQUM1QyxpQkFBaUIsR0FBRyxTQUFTLENBQUM7QUFDbEMsQ0FBQztBQU5ELDREQU1DIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IGZzIGZyb20gXCJmcy9wcm9taXNlc1wiO1xuXG4vLyBQT1JURUQgQ09ERTogc2VjdXJpdHkvYXV0aG9yaXplZEtleXMvZGFlbW9uL3BvcnRzZWN1cmVEYWVtb24uanMgZW1iZWRzIGEgaGFuZCBwb3J0IG9mIHRoaXMgZmlsZSBpbiBwbGFpbiBKUywgc28gdGhlXG4vLyBkYWVtb24gY2FuIHJ1biB3aXRoIG5vIGRlcGVuZGVuY2llcy4gVGhlIHR3byBhcmUgZXhwZWN0ZWQgdG8gYmVoYXZlIGlkZW50aWNhbGx5IC0gaWYgeW91IGNoYW5nZVxuLy8gb25lLCBtYWtlIHRoZSBtYXRjaGluZyBjaGFuZ2UgaW4gdGhlIG90aGVyLlxuXG5leHBvcnQgY29uc3QgREVGQVVMVF9XRUJIT09LX0ZJTEVfUEFUSCA9IFwiL2V0Yy9wb3J0c2VjdXJlL2Rpc2NvcmQtd2ViaG9va1wiO1xuY29uc3QgREVGQVVMVF9DSEVDS19JTlRFUlZBTCA9IDUgKiA2MCAqIDEwMDA7XG5jb25zdCBWQUxJRF9XRUJIT09LX1BSRUZJWEVTID0gW1xuICAgIFwiaHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvXCIsXG4gICAgXCJodHRwczovL2Rpc2NvcmRhcHAuY29tL2FwaS93ZWJob29rcy9cIixcbiAgICBcImh0dHBzOi8vY2FuYXJ5LmRpc2NvcmQuY29tL2FwaS93ZWJob29rcy9cIixcbl07XG5jb25zdCBESVNDT1JEX01FU1NBR0VfTElNSVQgPSAyMDAwO1xuY29uc3QgTUFYX0VSUk9SX0JPRFlfTEVOR1RIID0gNTAwO1xuY29uc3QgTUFYX1NFTkRfQVRURU1QVFMgPSAzO1xuY29uc3QgUkVEQUNURURfVE9LRU5fVklTSUJMRSA9IDg7XG5jb25zdCBERUZBVUxUX1JBVEVfTElNSVRfV0FJVCA9IDIgKiAxMDAwO1xuXG5sZXQgbm90aWZpY2F0aW9uU3RhdGU6IHtcbiAgICBmaWxlUGF0aDogc3RyaW5nO1xuICAgIHdlYmhvb2tVUkw6IHN0cmluZztcbiAgICBjaGVja1RpbWVyOiBOb2RlSlMuVGltZW91dDtcbn0gfCB1bmRlZmluZWQ7XG5cbi8vIERpc2NvcmQgd2ViaG9va3MgYXJlIHJhdGUgbGltaXRlZCAocm91Z2hseSA1IHJlcXVlc3RzIHBlciAyIHNlY29uZHMpLCBzbyBldmVyeSBzZW5kXG4vLyBnb2VzIHRocm91Z2ggb25lIGNoYWluIGluc3RlYWQgb2YgcmFjaW5nLlxubGV0IHNlbmRDaGFpbjogUHJvbWlzZTx1bmtub3duPiA9IFByb21pc2UucmVzb2x2ZSgpO1xuXG4vKiogUHVsbHMgdGhlIHdlYmhvb2sgVVJMIG91dCBvZiB3ZWJob29rIGZpbGUgY29udGVudHMuIGBzb3VyY2VOYW1lYCBvbmx5IGFwcGVhcnMgaW4gZXJyb3JzLCBzb1xuICAgIGNhbGxlcnMgY2FuIG5hbWUgYSByZW1vdGUgcGF0aC4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwYXJzZVdlYmhvb2tGaWxlKGNvbmZpZzogeyBjb250ZW50czogc3RyaW5nOyBzb3VyY2VOYW1lOiBzdHJpbmcgfSkge1xuICAgIGxldCB7IGNvbnRlbnRzLCBzb3VyY2VOYW1lIH0gPSBjb25maWc7XG4gICAgbGV0IGxpbmVzID0gY29udGVudHMuc3BsaXQoXCJcXG5cIikubWFwKGxpbmUgPT4gbGluZS50cmltKCkpLmZpbHRlcihsaW5lID0+IGxpbmUgJiYgIWxpbmUuc3RhcnRzV2l0aChcIiNcIikpO1xuICAgIGxldCB3ZWJob29rVVJMID0gbGluZXNbMF07XG4gICAgaWYgKCF3ZWJob29rVVJMKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgRXhwZWN0ZWQgYSBEaXNjb3JkIHdlYmhvb2sgVVJMIGluICR7c291cmNlTmFtZX0sIHRoZSBmaWxlIGhhcyBubyB1c2FibGUgbGluZXNgKTtcbiAgICB9XG4gICAgaWYgKCFWQUxJRF9XRUJIT09LX1BSRUZJWEVTLnNvbWUocHJlZml4ID0+IHdlYmhvb2tVUkwuc3RhcnRzV2l0aChwcmVmaXgpKSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICBgRXhwZWN0ZWQgYSBEaXNjb3JkIHdlYmhvb2sgVVJMIHN0YXJ0aW5nIHdpdGggb25lIG9mICR7VkFMSURfV0VCSE9PS19QUkVGSVhFUy5qb2luKFwiLCBcIil9LCBgXG4gICAgICAgICAgICArIGB3YXMgJHt3ZWJob29rVVJMLnNsaWNlKDAsIE1BWF9FUlJPUl9CT0RZX0xFTkdUSCl9IChpbiAke3NvdXJjZU5hbWV9KWBcbiAgICAgICAgKTtcbiAgICB9XG4gICAgcmV0dXJuIHdlYmhvb2tVUkw7XG59XG5cbi8qKiBLZWVwcyB0aGUgaWQgYW5kIGJvdGggZW5kcyBvZiB0aGUgdG9rZW4sIHNvIGEgcmVhZGVyIGNhbiBjb25maXJtIHdoaWNoIHdlYmhvb2sgcmVwbGFjZWQgdGhlaXJzXG4gICAgd2l0aG91dCByZWNlaXZpbmcgb25lIHRoZXkgY291bGQgcG9zdCB0by4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZWRhY3RXZWJob29rVVJMKHdlYmhvb2tVUkw6IHN0cmluZykge1xuICAgIGxldCBzZXBhcmF0b3JJbmRleCA9IHdlYmhvb2tVUkwubGFzdEluZGV4T2YoXCIvXCIpO1xuICAgIGxldCBiYXNlID0gd2ViaG9va1VSTC5zbGljZSgwLCBzZXBhcmF0b3JJbmRleCArIDEpO1xuICAgIGxldCB0b2tlbiA9IHdlYmhvb2tVUkwuc2xpY2Uoc2VwYXJhdG9ySW5kZXggKyAxKTtcbiAgICBpZiAodG9rZW4ubGVuZ3RoIDw9IFJFREFDVEVEX1RPS0VOX1ZJU0lCTEUgKiAyKSB7XG4gICAgICAgIC8vIFRvbyBzaG9ydCB0byBzaG93IGJvdGggZW5kcyB3aXRob3V0IGdpdmluZyBhd2F5IHRoZSB3aG9sZSB0b2tlbi5cbiAgICAgICAgcmV0dXJuIGAke2Jhc2V9JHt0b2tlbi5zbGljZSgwLCBSRURBQ1RFRF9UT0tFTl9WSVNJQkxFKX0uLi5gO1xuICAgIH1cbiAgICByZXR1cm4gYCR7YmFzZX0ke3Rva2VuLnNsaWNlKDAsIFJFREFDVEVEX1RPS0VOX1ZJU0lCTEUpfS4uLiR7dG9rZW4uc2xpY2UoLVJFREFDVEVEX1RPS0VOX1ZJU0lCTEUpfWA7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBmb3JtYXRXZWJob29rRmlsZSh3ZWJob29rVVJMOiBzdHJpbmcpIHtcbiAgICByZXR1cm4gYCMgcG9ydHNlY3VyZSBEaXNjb3JkIHdlYmhvb2suIEZpcnN0IG5vbi1jb21tZW50IGxpbmUgaXMgdXNlZC5cXG4ke3dlYmhvb2tVUkx9XFxuYDtcbn1cblxuYXN5bmMgZnVuY3Rpb24gcmVhZFdlYmhvb2tGaWxlKGZpbGVQYXRoOiBzdHJpbmcpIHtcbiAgICBsZXQgY29udGVudHM7XG4gICAgdHJ5IHtcbiAgICAgICAgY29udGVudHMgPSBhd2FpdCBmcy5yZWFkRmlsZShmaWxlUGF0aCwgXCJ1dGY4XCIpO1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBFeHBlY3RlZCBhIHJlYWRhYmxlIERpc2NvcmQgd2ViaG9vayBmaWxlIGF0ICR7ZmlsZVBhdGh9LCAke2V9YCk7XG4gICAgfVxuICAgIHJldHVybiBwYXJzZVdlYmhvb2tGaWxlKHsgY29udGVudHMsIHNvdXJjZU5hbWU6IGZpbGVQYXRoIH0pO1xufVxuXG4vKiogV2hlbiB0aGUgbWVzc2FnZSB3YXMgc2VudCwgaW4gdGhlIHNlbmRpbmcgbWFjaGluZSdzIG93biB0aW1lIHpvbmUuIERpc2NvcmQgc2hvd3Mgd2hlbiBpdFxuICAgIHJlY2VpdmVkIHNvbWV0aGluZywgd2hpY2ggaXMgbm90IHRoZSBzYW1lIHRoaW5nIHdoZW4gYSBtYWNoaW5lIGhhcyBiZWVuIG9mZmxpbmUgb3IgYSBzZW5kIGhhc1xuICAgIGJlZW4gcmV0cmllZCwgYW5kIHRoZSB6b25lIG1hdHRlcnMgd2hlbiB0aGUgbWFjaGluZXMgYXJlIG5vdCBhbGwgaW4gb25lIHBsYWNlLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIG1lc3NhZ2VUaW1lc3RhbXAobm93OiBEYXRlKSB7XG4gICAgbGV0IGRhdGUgPSBub3cudG9Mb2NhbGVEYXRlU3RyaW5nKFwiZW4tQ0FcIik7XG4gICAgbGV0IHRpbWUgPSBub3cudG9Mb2NhbGVUaW1lU3RyaW5nKFwiZW4tVVNcIiwge1xuICAgICAgICBob3VyOiBcIjItZGlnaXRcIixcbiAgICAgICAgbWludXRlOiBcIjItZGlnaXRcIixcbiAgICAgICAgc2Vjb25kOiBcIjItZGlnaXRcIixcbiAgICAgICAgaG91cjEyOiB0cnVlLFxuICAgICAgICB0aW1lWm9uZU5hbWU6IFwic2hvcnRcIixcbiAgICB9KTtcbiAgICByZXR1cm4gYCR7ZGF0ZX0gJHt0aW1lfWA7XG59XG5cbmFzeW5jIGZ1bmN0aW9uIHBvc3RUb1dlYmhvb2sod2ViaG9va1VSTDogc3RyaW5nLCBtZXNzYWdlOiBzdHJpbmcpIHtcbiAgICAvLyBTdGFtcGVkIG9uY2UgcmF0aGVyIHRoYW4gcGVyIGF0dGVtcHQsIHNvIGEgcmV0cnkgc2F5cyB3aGVuIHRoZSB0aGluZyBoYXBwZW5lZCByYXRoZXIgdGhhblxuICAgIC8vIHdoZW4gd2UgbGFzdCBtYW5hZ2VkIHRvIGdldCBpdCBvdXQuXG4gICAgbGV0IGNvbnRlbnQgPSBgXFxgJHttZXNzYWdlVGltZXN0YW1wKG5ldyBEYXRlKCkpfVxcYCAke21lc3NhZ2V9YDtcbiAgICBpZiAoY29udGVudC5sZW5ndGggPiBESVNDT1JEX01FU1NBR0VfTElNSVQpIHtcbiAgICAgICAgY29udGVudCA9IGNvbnRlbnQuc2xpY2UoMCwgRElTQ09SRF9NRVNTQUdFX0xJTUlUIC0gMykgKyBcIi4uLlwiO1xuICAgIH1cbiAgICBmb3IgKGxldCBhdHRlbXB0ID0gMTsgYXR0ZW1wdCA8PSBNQVhfU0VORF9BVFRFTVBUUzsgYXR0ZW1wdCsrKSB7XG4gICAgICAgIGxldCByZXNwb25zZSA9IGF3YWl0IGZldGNoKHdlYmhvb2tVUkwsIHtcbiAgICAgICAgICAgIG1ldGhvZDogXCJQT1NUXCIsXG4gICAgICAgICAgICBoZWFkZXJzOiB7IFwiQ29udGVudC1UeXBlXCI6IFwiYXBwbGljYXRpb24vanNvblwiIH0sXG4gICAgICAgICAgICBib2R5OiBKU09OLnN0cmluZ2lmeSh7IGNvbnRlbnQgfSksXG4gICAgICAgIH0pO1xuICAgICAgICBpZiAocmVzcG9uc2Uub2spIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBsZXQgYm9keSA9IChhd2FpdCByZXNwb25zZS50ZXh0KCkpLnNsaWNlKDAsIE1BWF9FUlJPUl9CT0RZX0xFTkdUSCk7XG4gICAgICAgIGlmIChyZXNwb25zZS5zdGF0dXMgPT09IDQyOSAmJiBhdHRlbXB0IDwgTUFYX1NFTkRfQVRURU1QVFMpIHtcbiAgICAgICAgICAgIGxldCByZXRyeUFmdGVyID0gTnVtYmVyKHJlc3BvbnNlLmhlYWRlcnMuZ2V0KFwicmV0cnktYWZ0ZXJcIikpO1xuICAgICAgICAgICAgbGV0IHdhaXRUaW1lID0gcmV0cnlBZnRlciAmJiByZXRyeUFmdGVyICogMTAwMCB8fCBERUZBVUxUX1JBVEVfTElNSVRfV0FJVDtcbiAgICAgICAgICAgIGF3YWl0IG5ldyBQcm9taXNlKHJlc29sdmUgPT4gc2V0VGltZW91dChyZXNvbHZlLCB3YWl0VGltZSkpO1xuICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgYEV4cGVjdGVkIGEgMnh4IHJlc3BvbnNlIGZyb20gdGhlIERpc2NvcmQgd2ViaG9vaywgd2FzICR7cmVzcG9uc2Uuc3RhdHVzfSAke3Jlc3BvbnNlLnN0YXR1c1RleHR9LCBib2R5ICR7Ym9keX1gXG4gICAgICAgICk7XG4gICAgfVxufVxuXG5mdW5jdGlvbiBxdWV1ZVNlbmQod2ViaG9va1VSTDogc3RyaW5nLCBtZXNzYWdlOiBzdHJpbmcpIHtcbiAgICBsZXQgc2VuZCA9ICgpID0+IHBvc3RUb1dlYmhvb2sod2ViaG9va1VSTCwgbWVzc2FnZSk7XG4gICAgbGV0IHJlc3VsdCA9IHNlbmRDaGFpbi50aGVuKHNlbmQsIHNlbmQpO1xuICAgIHNlbmRDaGFpbiA9IHJlc3VsdC5jYXRjaCgoKSA9PiB1bmRlZmluZWQpO1xuICAgIHJldHVybiByZXN1bHQ7XG59XG5cbi8qKiBTZW5kcyB0byBhbiBleHBsaWNpdCB3ZWJob29rIFVSTCwgZm9yIHRvb2xpbmcgdGhhdCBhY3RzIG9uIGEgd2ViaG9vayBiZWZvcmUgKG9yIGluc3RlYWQgb2YpXG4gICAgY29uZmlndXJlRGlzY29yZE5vdGlmaWNhdGlvbnMgLSBzZXR1cCwgbWlncmF0aW9ucywgY29ubmVjdGl2aXR5IGNoZWNrcy4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzZW5kVG9XZWJob29rVVJMKGNvbmZpZzogeyB3ZWJob29rVVJMOiBzdHJpbmc7IG1lc3NhZ2U6IHN0cmluZyB9KSB7XG4gICAgcmV0dXJuIHF1ZXVlU2VuZChjb25maWcud2ViaG9va1VSTCwgY29uZmlnLm1lc3NhZ2UpO1xufVxuXG5hc3luYyBmdW5jdGlvbiBjaGVja1dlYmhvb2tGaWxlQ2hhbmdlZCgpIHtcbiAgICBsZXQgc3RhdGUgPSBub3RpZmljYXRpb25TdGF0ZTtcbiAgICBpZiAoIXN0YXRlKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgbGV0IG5ld1dlYmhvb2tVUkw7XG4gICAgdHJ5IHtcbiAgICAgICAgbmV3V2ViaG9va1VSTCA9IGF3YWl0IHJlYWRXZWJob29rRmlsZShzdGF0ZS5maWxlUGF0aCk7XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgICBjb25zb2xlLmVycm9yKGBwb3J0c2VjdXJlOiBEaXNjb3JkIHdlYmhvb2sgZmlsZSBpcyBubyBsb25nZXIgcmVhZGFibGUsIHN0aWxsIHVzaW5nIHRoZSBsb2FkZWQgd2ViaG9vay4gJHtlfWApO1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmIChuZXdXZWJob29rVVJMID09PSBzdGF0ZS53ZWJob29rVVJMKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgLy8gV2FybiB0aGUgb2xkIGNoYW5uZWwgZmlyc3QsIHdpdGggdGhlIG5ldyB3ZWJob29rIHJlZGFjdGVkLCBzbyBhIHN0b2xlbiBvbGQgd2ViaG9vayBkb2VzIG5vdFxuICAgIC8vIGhhbmQgdGhlIGF0dGFja2VyIGEgdXNhYmxlIG5ldyBvbmUuXG4gICAgdHJ5IHtcbiAgICAgICAgYXdhaXQgcXVldWVTZW5kKFxuICAgICAgICAgICAgc3RhdGUud2ViaG9va1VSTCxcbiAgICAgICAgICAgIGAqKnBvcnRzZWN1cmUqKjogdGhlIERpc2NvcmQgd2ViaG9vayBpbiBcXGAke3N0YXRlLmZpbGVQYXRofVxcYCBjaGFuZ2VkIHRvYFxuICAgICAgICAgICAgKyBgIFxcYCR7cmVkYWN0V2ViaG9va1VSTChuZXdXZWJob29rVVJMKX1cXGAuYFxuICAgICAgICAgICAgKyBgIE5vdGlmaWNhdGlvbnMgYXJlIG1vdmluZyB0byB0aGUgbmV3IHdlYmhvb2sgYW5kIHRoaXMgY2hhbm5lbCB3aWxsIHN0b3AgcmVjZWl2aW5nIHRoZW0uYFxuICAgICAgICApO1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgY29uc29sZS5lcnJvcihgcG9ydHNlY3VyZTogZmFpbGVkIHRvIHdhcm4gdGhlIG9sZCBEaXNjb3JkIHdlYmhvb2sgYWJvdXQgdGhlIGNoYW5nZS4gJHtlfWApO1xuICAgIH1cbiAgICBzdGF0ZS53ZWJob29rVVJMID0gbmV3V2ViaG9va1VSTDtcbn1cblxuLyoqIE11c3QgYmUgY2FsbGVkIG9uY2Ugb24gc3RhcnR1cCwgYmVmb3JlIGFueSBub3RpZmljYXRpb24gaXMgc2VudC4gQWJvcnRzIHRoZSBwcm9jZXNzIGlmIHRoZVxuICAgIHdlYmhvb2sgZmlsZSBpcyBtaXNzaW5nIG9yIGludmFsaWQsIHRoZW4gcmUtY2hlY2tzIHRoZSBmaWxlIG9uIGFuIGludGVydmFsIGFuZCB3YXJucyB0aGUgb2xkXG4gICAgd2ViaG9vayB3aGVuZXZlciBpdCBjaGFuZ2VzLiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNvbmZpZ3VyZURpc2NvcmROb3RpZmljYXRpb25zKGNvbmZpZz86IHtcbiAgICBmaWxlUGF0aD86IHN0cmluZztcbiAgICBjaGVja0ludGVydmFsPzogbnVtYmVyO1xufSkge1xuICAgIGlmIChub3RpZmljYXRpb25TdGF0ZSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIGNvbmZpZ3VyZURpc2NvcmROb3RpZmljYXRpb25zIHRvIGJlIGNhbGxlZCBvbmNlLCB3YXMgY2FsbGVkIGFnYWluIChhbHJlYWR5IHVzaW5nICR7bm90aWZpY2F0aW9uU3RhdGUuZmlsZVBhdGh9KWApO1xuICAgIH1cbiAgICBsZXQgZmlsZVBhdGggPSBjb25maWc/LmZpbGVQYXRoIHx8IERFRkFVTFRfV0VCSE9PS19GSUxFX1BBVEg7XG4gICAgbGV0IGNoZWNrSW50ZXJ2YWwgPSBjb25maWc/LmNoZWNrSW50ZXJ2YWwgfHwgREVGQVVMVF9DSEVDS19JTlRFUlZBTDtcbiAgICBsZXQgd2ViaG9va1VSTDtcbiAgICB0cnkge1xuICAgICAgICB3ZWJob29rVVJMID0gYXdhaXQgcmVhZFdlYmhvb2tGaWxlKGZpbGVQYXRoKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIGNvbnNvbGUuZXJyb3IoYHBvcnRzZWN1cmU6IHJlZnVzaW5nIHRvIHN0YXJ0IHdpdGhvdXQgYSB2YWxpZCBEaXNjb3JkIHdlYmhvb2sgZmlsZS5cXG4ke2V9YCk7XG4gICAgICAgIHByb2Nlc3MuZXhpdCgxKTtcbiAgICB9XG4gICAgbGV0IGNoZWNrVGltZXIgPSBzZXRJbnRlcnZhbCgoKSA9PiB7XG4gICAgICAgIGNoZWNrV2ViaG9va0ZpbGVDaGFuZ2VkKCkuY2F0Y2goZSA9PiBjb25zb2xlLmVycm9yKGBwb3J0c2VjdXJlOiBEaXNjb3JkIHdlYmhvb2sgZmlsZSBjaGVjayBmYWlsZWQuICR7ZX1gKSk7XG4gICAgfSwgY2hlY2tJbnRlcnZhbCk7XG4gICAgLy8gVGhlIGNoZWNrIHNob3VsZCBuZXZlciBiZSB0aGUgcmVhc29uIHRoZSBwcm9jZXNzIHN0YXlzIGFsaXZlLlxuICAgIGNoZWNrVGltZXIudW5yZWYoKTtcbiAgICBub3RpZmljYXRpb25TdGF0ZSA9IHsgZmlsZVBhdGgsIHdlYmhvb2tVUkwsIGNoZWNrVGltZXIgfTtcbiAgICByZXR1cm4geyBmaWxlUGF0aCwgY2hlY2tJbnRlcnZhbCB9O1xufVxuXG4vLyBETyBOT1QgYWRkIG5ldyBjYWxscyB0byB0aGlzLiBFdmVyeSBtZXNzYWdlIGdvZXMgdG8gYSByZWFsIERpc2NvcmQgc2VydmVyIHNvbWVvbmUgcmVhZHMsIHNvIGFcbi8vIG5vdGlmaWNhdGlvbiBpcyBvbmx5IGV2ZXIgYWRkZWQgd2hlbiB0aGUgdXNlciBleHBsaWNpdGx5IGFza3MgZm9yIHRoYXQgc3BlY2lmaWMgY2FzZS4gU3RhcnR1cCxcbi8vIHN1Y2Nlc3MsIGVycm9ycywgcmV0cmllcyBhbmQgcmVjb3ZlcmllcyBhbGwgYmVsb25nIGluIGEgbG9nIGluc3RlYWQuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gc2VuZERpc2NvcmROb3RpZmljYXRpb24obWVzc2FnZTogc3RyaW5nKSB7XG4gICAgbGV0IHN0YXRlID0gbm90aWZpY2F0aW9uU3RhdGU7XG4gICAgaWYgKCFzdGF0ZSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIGNvbmZpZ3VyZURpc2NvcmROb3RpZmljYXRpb25zIHRvIGJlIGNhbGxlZCBiZWZvcmUgc2VuZGluZywgd2FzIGNhbGxlZCB3aXRoIG1lc3NhZ2UgJHttZXNzYWdlLnNsaWNlKDAsIE1BWF9FUlJPUl9CT0RZX0xFTkdUSCl9YCk7XG4gICAgfVxuICAgIGF3YWl0IHF1ZXVlU2VuZChzdGF0ZS53ZWJob29rVVJMLCBtZXNzYWdlKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHN0b3BEaXNjb3JkTm90aWZpY2F0aW9ucygpIHtcbiAgICBpZiAoIW5vdGlmaWNhdGlvblN0YXRlKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY2xlYXJJbnRlcnZhbChub3RpZmljYXRpb25TdGF0ZS5jaGVja1RpbWVyKTtcbiAgICBub3RpZmljYXRpb25TdGF0ZSA9IHVuZGVmaW5lZDtcbn1cbiJdfQ==
|
|
197
|
+
/* _JS_SOURCE_HASH = "be450922cec9e8a268e2a78f817bfedb88063a0b59db5b1a954d81c96169543a"; */
|
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import { parseWebhookFile } from "./discord";
|
|
2
2
|
import { ensureRemoteWebhook, REPLACE_KEYWORD } from "./remoteWebhook";
|
|
3
|
+
import { describeHost, THIS_MACHINE } from "../helpers/remoteSSH";
|
|
3
4
|
|
|
4
|
-
const USAGE = `Usage: yarn setupnotify
|
|
5
|
+
const USAGE = `Usage: yarn setupnotify [host] <discord-webhook-url> [${REPLACE_KEYWORD}]
|
|
6
|
+
|
|
7
|
+
With no host it sets the webhook up on this machine.`;
|
|
5
8
|
|
|
6
9
|
function parseArgs(argv: string[]) {
|
|
7
10
|
let replace = argv.includes(REPLACE_KEYWORD);
|
|
8
11
|
let positional = argv.filter(arg => arg !== REPLACE_KEYWORD);
|
|
9
|
-
if (positional.length
|
|
10
|
-
throw new Error(
|
|
12
|
+
if (!positional.length || positional.length > 2) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
`Expected a webhook URL, and optionally a host before it, was ${positional.length}`
|
|
15
|
+
+ ` argument(s): ${positional.join(" ") || "(none)"}\n${USAGE}`
|
|
16
|
+
);
|
|
11
17
|
}
|
|
12
|
-
|
|
13
|
-
//
|
|
18
|
+
// The webhook url is the last one, so whatever comes before it is the host. Without one this
|
|
19
|
+
// machine is the target.
|
|
20
|
+
let webhookURL = positional[positional.length - 1];
|
|
21
|
+
let host = positional.length > 1 && positional[0] || THIS_MACHINE;
|
|
22
|
+
// Validates the URL shape up front, so we never go anywhere with a bad webhook.
|
|
14
23
|
parseWebhookFile({ contents: webhookURL, sourceName: "the command line" });
|
|
15
24
|
return { host, webhookURL, replace };
|
|
16
25
|
}
|
|
@@ -19,11 +28,11 @@ export async function main() {
|
|
|
19
28
|
let { host, webhookURL, replace } = parseArgs(process.argv.slice(2));
|
|
20
29
|
let { outcome, filePath } = await ensureRemoteWebhook({ host, webhookURL, replace });
|
|
21
30
|
if (outcome === "unchanged") {
|
|
22
|
-
console.log(`${host} already has this exact webhook in ${filePath}, nothing to do.`);
|
|
31
|
+
console.log(`${describeHost(host)} already has this exact webhook in ${filePath}, nothing to do.`);
|
|
23
32
|
return;
|
|
24
33
|
}
|
|
25
34
|
if (outcome === "replaced") {
|
|
26
|
-
console.log(`Replaced the webhook on ${host} and notified the old one.`);
|
|
35
|
+
console.log(`Replaced the webhook on ${describeHost(host)} and notified the old one.`);
|
|
27
36
|
}
|
|
28
|
-
console.log(`Wrote ${filePath} on ${host} (mode 600), and confirmed on the new webhook.`);
|
|
37
|
+
console.log(`Wrote ${filePath} on ${describeHost(host)} (mode 600), and confirmed on the new webhook.`);
|
|
29
38
|
}
|