sliftutils 1.7.124 → 1.7.126
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/CLAUDE.md +3 -1
- package/bin/derivekey.js +10 -0
- package/bin/portsecuredaemon.js +13 -0
- package/bin/securessh.js +10 -0
- package/bin/setupnotify.js +9 -0
- package/bin/signfiles.js +10 -0
- package/bin/unrevoke.js +9 -0
- package/package.json +14 -3
- package/security/README.md +141 -0
- package/security/authorizedKeys/authorizedKeys.ts +66 -0
- package/security/authorizedKeys/daemon/authLog.ts +117 -0
- package/security/authorizedKeys/daemon/daemon.ts +308 -0
- package/security/authorizedKeys/daemon/git.ts +119 -0
- package/security/authorizedKeys/daemon/notify.ts +25 -0
- package/security/authorizedKeys/daemon/paths.ts +26 -0
- package/security/authorizedKeys/daemon/portsecure.service +19 -0
- package/security/authorizedKeys/daemon/revocation.ts +306 -0
- package/security/authorizedKeys/daemon/rootKeys.ts +135 -0
- package/security/authorizedKeys/daemon/sshdConfig.ts +85 -0
- package/security/authorizedKeys/daemon/state.ts +108 -0
- package/security/authorizedKeys/daemon/trust.ts +291 -0
- package/security/authorizedKeys/daemon/userKeys.ts +76 -0
- package/security/authorizedKeys/dist/authorizedKeys.ts.cache +73 -0
- package/security/authorizedKeys/dist/revokeSource.ts.cache +44 -0
- package/security/authorizedKeys/dist/secureSSH.ts.cache +552 -0
- package/security/authorizedKeys/dist/sources.ts.cache +24 -0
- package/security/authorizedKeys/dist/unrevoke.ts.cache +145 -0
- package/security/authorizedKeys/revokeSource.ts +40 -0
- package/security/authorizedKeys/secureSSH.ts +613 -0
- package/security/authorizedKeys/sources.ts +20 -0
- package/security/authorizedKeys/unrevoke.ts +149 -0
- package/security/helpers/dist/paths.ts.cache +28 -0
- package/security/helpers/dist/remoteSSH.ts.cache +90 -0
- package/security/helpers/dist/spawn.ts.cache +34 -0
- package/security/helpers/paths.ts +20 -0
- package/security/helpers/remoteSSH.ts +95 -0
- package/security/helpers/spawn.ts +36 -0
- package/security/keys/deriveKey.ts +72 -0
- package/security/keys/dist/deriveKey.ts.cache +72 -0
- package/security/keys/dist/sshKeyFile.ts.cache +153 -0
- package/security/keys/sshKeyFile.ts +156 -0
- package/security/notifications/discord.ts +190 -0
- package/security/notifications/dist/discord.ts.cache +180 -0
- package/security/notifications/remoteWebhook.ts +85 -0
- package/security/notifications/setupNotify.ts +29 -0
- package/security/signedFiles/dist/manifest.ts.cache +68 -0
- package/security/signedFiles/dist/signFiles.ts.cache +146 -0
- package/security/signedFiles/manifest.ts +69 -0
- package/security/signedFiles/signFiles.ts +151 -0
- package/storage/BulkDatabase2/dist/BulkDatabaseBase.ts.cache +17 -20
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule && mod.default) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
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;
|
|
7
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
|
+
// PORTED CODE: security/authorizedKeys/daemon/portsecureDaemon.js embeds a hand port of this file in plain JS, so the
|
|
9
|
+
// daemon can run with no dependencies. The two are expected to behave identically - if you change
|
|
10
|
+
// one, make the matching change in the other.
|
|
11
|
+
exports.DEFAULT_WEBHOOK_FILE_PATH = "/etc/portsecure/discord-webhook";
|
|
12
|
+
const DEFAULT_CHECK_INTERVAL = 5 * 60 * 1000;
|
|
13
|
+
const VALID_WEBHOOK_PREFIXES = [
|
|
14
|
+
"https://discord.com/api/webhooks/",
|
|
15
|
+
"https://discordapp.com/api/webhooks/",
|
|
16
|
+
"https://canary.discord.com/api/webhooks/",
|
|
17
|
+
];
|
|
18
|
+
const DISCORD_MESSAGE_LIMIT = 2000;
|
|
19
|
+
const MAX_ERROR_BODY_LENGTH = 500;
|
|
20
|
+
const MAX_SEND_ATTEMPTS = 3;
|
|
21
|
+
const REDACTED_TOKEN_VISIBLE = 8;
|
|
22
|
+
const DEFAULT_RATE_LIMIT_WAIT = 2 * 1000;
|
|
23
|
+
let notificationState;
|
|
24
|
+
// Discord webhooks are rate limited (roughly 5 requests per 2 seconds), so every send
|
|
25
|
+
// goes through one chain instead of racing.
|
|
26
|
+
let sendChain = Promise.resolve();
|
|
27
|
+
/** Pulls the webhook URL out of webhook file contents. `sourceName` only appears in errors, so
|
|
28
|
+
callers can name a remote path. */
|
|
29
|
+
function parseWebhookFile(config) {
|
|
30
|
+
let { contents, sourceName } = config;
|
|
31
|
+
let lines = contents.split("\n").map(line => line.trim()).filter(line => line && !line.startsWith("#"));
|
|
32
|
+
let webhookURL = lines[0];
|
|
33
|
+
if (!webhookURL) {
|
|
34
|
+
throw new Error(`Expected a Discord webhook URL in ${sourceName}, the file has no usable lines`);
|
|
35
|
+
}
|
|
36
|
+
if (!VALID_WEBHOOK_PREFIXES.some(prefix => webhookURL.startsWith(prefix))) {
|
|
37
|
+
throw new Error(`Expected a Discord webhook URL starting with one of ${VALID_WEBHOOK_PREFIXES.join(", ")}, `
|
|
38
|
+
+ `was ${webhookURL.slice(0, MAX_ERROR_BODY_LENGTH)} (in ${sourceName})`);
|
|
39
|
+
}
|
|
40
|
+
return webhookURL;
|
|
41
|
+
}
|
|
42
|
+
exports.parseWebhookFile = parseWebhookFile;
|
|
43
|
+
/** Keeps the id and both ends of the token, so a reader can confirm which webhook replaced theirs
|
|
44
|
+
without receiving one they could post to. */
|
|
45
|
+
function redactWebhookURL(webhookURL) {
|
|
46
|
+
let separatorIndex = webhookURL.lastIndexOf("/");
|
|
47
|
+
let base = webhookURL.slice(0, separatorIndex + 1);
|
|
48
|
+
let token = webhookURL.slice(separatorIndex + 1);
|
|
49
|
+
if (token.length <= REDACTED_TOKEN_VISIBLE * 2) {
|
|
50
|
+
// Too short to show both ends without giving away the whole token.
|
|
51
|
+
return `${base}${token.slice(0, REDACTED_TOKEN_VISIBLE)}...`;
|
|
52
|
+
}
|
|
53
|
+
return `${base}${token.slice(0, REDACTED_TOKEN_VISIBLE)}...${token.slice(-REDACTED_TOKEN_VISIBLE)}`;
|
|
54
|
+
}
|
|
55
|
+
exports.redactWebhookURL = redactWebhookURL;
|
|
56
|
+
function formatWebhookFile(webhookURL) {
|
|
57
|
+
return `# portsecure Discord webhook. First non-comment line is used.\n${webhookURL}\n`;
|
|
58
|
+
}
|
|
59
|
+
exports.formatWebhookFile = formatWebhookFile;
|
|
60
|
+
async function readWebhookFile(filePath) {
|
|
61
|
+
let contents;
|
|
62
|
+
try {
|
|
63
|
+
contents = await promises_1.default.readFile(filePath, "utf8");
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
throw new Error(`Expected a readable Discord webhook file at ${filePath}, ${e}`);
|
|
67
|
+
}
|
|
68
|
+
return parseWebhookFile({ contents, sourceName: filePath });
|
|
69
|
+
}
|
|
70
|
+
async function postToWebhook(webhookURL, message) {
|
|
71
|
+
let content = message;
|
|
72
|
+
if (content.length > DISCORD_MESSAGE_LIMIT) {
|
|
73
|
+
content = content.slice(0, DISCORD_MESSAGE_LIMIT - 3) + "...";
|
|
74
|
+
}
|
|
75
|
+
for (let attempt = 1; attempt <= MAX_SEND_ATTEMPTS; attempt++) {
|
|
76
|
+
let response = await fetch(webhookURL, {
|
|
77
|
+
method: "POST",
|
|
78
|
+
headers: { "Content-Type": "application/json" },
|
|
79
|
+
body: JSON.stringify({ content }),
|
|
80
|
+
});
|
|
81
|
+
if (response.ok) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
let body = (await response.text()).slice(0, MAX_ERROR_BODY_LENGTH);
|
|
85
|
+
if (response.status === 429 && attempt < MAX_SEND_ATTEMPTS) {
|
|
86
|
+
let retryAfter = Number(response.headers.get("retry-after"));
|
|
87
|
+
let waitTime = retryAfter && retryAfter * 1000 || DEFAULT_RATE_LIMIT_WAIT;
|
|
88
|
+
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
throw new Error(`Expected a 2xx response from the Discord webhook, was ${response.status} ${response.statusText}, body ${body}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function queueSend(webhookURL, message) {
|
|
95
|
+
let send = () => postToWebhook(webhookURL, message);
|
|
96
|
+
let result = sendChain.then(send, send);
|
|
97
|
+
sendChain = result.catch(() => undefined);
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
/** Sends to an explicit webhook URL, for tooling that acts on a webhook before (or instead of)
|
|
101
|
+
configureDiscordNotifications - setup, migrations, connectivity checks. */
|
|
102
|
+
function sendToWebhookURL(config) {
|
|
103
|
+
return queueSend(config.webhookURL, config.message);
|
|
104
|
+
}
|
|
105
|
+
exports.sendToWebhookURL = sendToWebhookURL;
|
|
106
|
+
async function checkWebhookFileChanged() {
|
|
107
|
+
let state = notificationState;
|
|
108
|
+
if (!state) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
let newWebhookURL;
|
|
112
|
+
try {
|
|
113
|
+
newWebhookURL = await readWebhookFile(state.filePath);
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
console.error(`portsecure: Discord webhook file is no longer readable, still using the loaded webhook. ${e}`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (newWebhookURL === state.webhookURL) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// Warn the old channel first, with the new webhook redacted, so a stolen old webhook does not
|
|
123
|
+
// hand the attacker a usable new one.
|
|
124
|
+
try {
|
|
125
|
+
await queueSend(state.webhookURL, `**portsecure**: the Discord webhook in \`${state.filePath}\` changed to`
|
|
126
|
+
+ ` \`${redactWebhookURL(newWebhookURL)}\`.`
|
|
127
|
+
+ ` Notifications are moving to the new webhook and this channel will stop receiving them.`);
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
console.error(`portsecure: failed to warn the old Discord webhook about the change. ${e}`);
|
|
131
|
+
}
|
|
132
|
+
state.webhookURL = newWebhookURL;
|
|
133
|
+
}
|
|
134
|
+
/** Must be called once on startup, before any notification is sent. Aborts the process if the
|
|
135
|
+
webhook file is missing or invalid, then re-checks the file on an interval and warns the old
|
|
136
|
+
webhook whenever it changes. */
|
|
137
|
+
async function configureDiscordNotifications(config) {
|
|
138
|
+
if (notificationState) {
|
|
139
|
+
throw new Error(`Expected configureDiscordNotifications to be called once, was called again (already using ${notificationState.filePath})`);
|
|
140
|
+
}
|
|
141
|
+
let filePath = (config === null || config === void 0 ? void 0 : config.filePath) || exports.DEFAULT_WEBHOOK_FILE_PATH;
|
|
142
|
+
let checkInterval = (config === null || config === void 0 ? void 0 : config.checkInterval) || DEFAULT_CHECK_INTERVAL;
|
|
143
|
+
let webhookURL;
|
|
144
|
+
try {
|
|
145
|
+
webhookURL = await readWebhookFile(filePath);
|
|
146
|
+
}
|
|
147
|
+
catch (e) {
|
|
148
|
+
console.error(`portsecure: refusing to start without a valid Discord webhook file.\n${e}`);
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
let checkTimer = setInterval(() => {
|
|
152
|
+
checkWebhookFileChanged().catch(e => console.error(`portsecure: Discord webhook file check failed. ${e}`));
|
|
153
|
+
}, checkInterval);
|
|
154
|
+
// The check should never be the reason the process stays alive.
|
|
155
|
+
checkTimer.unref();
|
|
156
|
+
notificationState = { filePath, webhookURL, checkTimer };
|
|
157
|
+
return { filePath, checkInterval };
|
|
158
|
+
}
|
|
159
|
+
exports.configureDiscordNotifications = configureDiscordNotifications;
|
|
160
|
+
// DO NOT add new calls to this. Every message goes to a real Discord server someone reads, so a
|
|
161
|
+
// notification is only ever added when the user explicitly asks for that specific case. Startup,
|
|
162
|
+
// success, errors, retries and recoveries all belong in a log instead.
|
|
163
|
+
async function sendDiscordNotification(message) {
|
|
164
|
+
let state = notificationState;
|
|
165
|
+
if (!state) {
|
|
166
|
+
throw new Error(`Expected configureDiscordNotifications to be called before sending, was called with message ${message.slice(0, MAX_ERROR_BODY_LENGTH)}`);
|
|
167
|
+
}
|
|
168
|
+
await queueSend(state.webhookURL, message);
|
|
169
|
+
}
|
|
170
|
+
exports.sendDiscordNotification = sendDiscordNotification;
|
|
171
|
+
function stopDiscordNotifications() {
|
|
172
|
+
if (!notificationState) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
clearInterval(notificationState.checkTimer);
|
|
176
|
+
notificationState = undefined;
|
|
177
|
+
}
|
|
178
|
+
exports.stopDiscordNotifications = stopDiscordNotifications;
|
|
179
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGlzY29yZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImRpc2NvcmQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUEsMkRBQTZCO0FBRTdCLHNIQUFzSDtBQUN0SCxrR0FBa0c7QUFDbEcsOENBQThDO0FBRWpDLFFBQUEseUJBQXlCLEdBQUcsaUNBQWlDLENBQUM7QUFDM0UsTUFBTSxzQkFBc0IsR0FBRyxDQUFDLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQztBQUM3QyxNQUFNLHNCQUFzQixHQUFHO0lBQzNCLG1DQUFtQztJQUNuQyxzQ0FBc0M7SUFDdEMsMENBQTBDO0NBQzdDLENBQUM7QUFDRixNQUFNLHFCQUFxQixHQUFHLElBQUksQ0FBQztBQUNuQyxNQUFNLHFCQUFxQixHQUFHLEdBQUcsQ0FBQztBQUNsQyxNQUFNLGlCQUFpQixHQUFHLENBQUMsQ0FBQztBQUM1QixNQUFNLHNCQUFzQixHQUFHLENBQUMsQ0FBQztBQUNqQyxNQUFNLHVCQUF1QixHQUFHLENBQUMsR0FBRyxJQUFJLENBQUM7QUFFekMsSUFBSSxpQkFJUyxDQUFDO0FBRWQsc0ZBQXNGO0FBQ3RGLDRDQUE0QztBQUM1QyxJQUFJLFNBQVMsR0FBcUIsT0FBTyxDQUFDLE9BQU8sRUFBRSxDQUFDO0FBRXBEO3NDQUNzQztBQUN0QyxTQUFnQixnQkFBZ0IsQ0FBQyxNQUFnRDtJQUM3RSxJQUFJLEVBQUUsUUFBUSxFQUFFLFVBQVUsRUFBRSxHQUFHLE1BQU0sQ0FBQztJQUN0QyxJQUFJLEtBQUssR0FBRyxRQUFRLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztJQUN4RyxJQUFJLFVBQVUsR0FBRyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDMUIsSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO1FBQ2QsTUFBTSxJQUFJLEtBQUssQ0FBQyxxQ0FBcUMsVUFBVSxnQ0FBZ0MsQ0FBQyxDQUFDO0lBQ3JHLENBQUM7SUFDRCxJQUFJLENBQUMsc0JBQXNCLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsVUFBVSxDQUFDLFVBQVUsQ0FBQyxNQUFNLENBQUMsQ0FBQyxFQUFFLENBQUM7UUFDeEUsTUFBTSxJQUFJLEtBQUssQ0FDWCx1REFBdUQsc0JBQXNCLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJO2NBQzFGLE9BQU8sVUFBVSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUscUJBQXFCLENBQUMsUUFBUSxVQUFVLEdBQUcsQ0FDM0UsQ0FBQztJQUNOLENBQUM7SUFDRCxPQUFPLFVBQVUsQ0FBQztBQUN0QixDQUFDO0FBZEQsNENBY0M7QUFFRDtnREFDZ0Q7QUFDaEQsU0FBZ0IsZ0JBQWdCLENBQUMsVUFBa0I7SUFDL0MsSUFBSSxjQUFjLEdBQUcsVUFBVSxDQUFDLFdBQVcsQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUNqRCxJQUFJLElBQUksR0FBRyxVQUFVLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxjQUFjLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDbkQsSUFBSSxLQUFLLEdBQUcsVUFBVSxDQUFDLEtBQUssQ0FBQyxjQUFjLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDakQsSUFBSSxLQUFLLENBQUMsTUFBTSxJQUFJLHNCQUFzQixHQUFHLENBQUMsRUFBRSxDQUFDO1FBQzdDLG1FQUFtRTtRQUNuRSxPQUFPLEdBQUcsSUFBSSxHQUFHLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLHNCQUFzQixDQUFDLEtBQUssQ0FBQztJQUNqRSxDQUFDO0lBQ0QsT0FBTyxHQUFHLElBQUksR0FBRyxLQUFLLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxzQkFBc0IsQ0FBQyxNQUFNLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQyxzQkFBc0IsQ0FBQyxFQUFFLENBQUM7QUFDeEcsQ0FBQztBQVRELDRDQVNDO0FBRUQsU0FBZ0IsaUJBQWlCLENBQUMsVUFBa0I7SUFDaEQsT0FBTyxrRUFBa0UsVUFBVSxJQUFJLENBQUM7QUFDNUYsQ0FBQztBQUZELDhDQUVDO0FBRUQsS0FBSyxVQUFVLGVBQWUsQ0FBQyxRQUFnQjtJQUMzQyxJQUFJLFFBQVEsQ0FBQztJQUNiLElBQUksQ0FBQztRQUNELFFBQVEsR0FBRyxNQUFNLGtCQUFFLENBQUMsUUFBUSxDQUFDLFFBQVEsRUFBRSxNQUFNLENBQUMsQ0FBQztJQUNuRCxDQUFDO0lBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztRQUNULE1BQU0sSUFBSSxLQUFLLENBQUMsK0NBQStDLFFBQVEsS0FBSyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQ3JGLENBQUM7SUFDRCxPQUFPLGdCQUFnQixDQUFDLEVBQUUsUUFBUSxFQUFFLFVBQVUsRUFBRSxRQUFRLEVBQUUsQ0FBQyxDQUFDO0FBQ2hFLENBQUM7QUFFRCxLQUFLLFVBQVUsYUFBYSxDQUFDLFVBQWtCLEVBQUUsT0FBZTtJQUM1RCxJQUFJLE9BQU8sR0FBRyxPQUFPLENBQUM7SUFDdEIsSUFBSSxPQUFPLENBQUMsTUFBTSxHQUFHLHFCQUFxQixFQUFFLENBQUM7UUFDekMsT0FBTyxHQUFHLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLHFCQUFxQixHQUFHLENBQUMsQ0FBQyxHQUFHLEtBQUssQ0FBQztJQUNsRSxDQUFDO0lBQ0QsS0FBSyxJQUFJLE9BQU8sR0FBRyxDQUFDLEVBQUUsT0FBTyxJQUFJLGlCQUFpQixFQUFFLE9BQU8sRUFBRSxFQUFFLENBQUM7UUFDNUQsSUFBSSxRQUFRLEdBQUcsTUFBTSxLQUFLLENBQUMsVUFBVSxFQUFFO1lBQ25DLE1BQU0sRUFBRSxNQUFNO1lBQ2QsT0FBTyxFQUFFLEVBQUUsY0FBYyxFQUFFLGtCQUFrQixFQUFFO1lBQy9DLElBQUksRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUUsT0FBTyxFQUFFLENBQUM7U0FDcEMsQ0FBQyxDQUFDO1FBQ0gsSUFBSSxRQUFRLENBQUMsRUFBRSxFQUFFLENBQUM7WUFDZCxPQUFPO1FBQ1gsQ0FBQztRQUNELElBQUksSUFBSSxHQUFHLENBQUMsTUFBTSxRQUFRLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLHFCQUFxQixDQUFDLENBQUM7UUFDbkUsSUFBSSxRQUFRLENBQUMsTUFBTSxLQUFLLEdBQUcsSUFBSSxPQUFPLEdBQUcsaUJBQWlCLEVBQUUsQ0FBQztZQUN6RCxJQUFJLFVBQVUsR0FBRyxNQUFNLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQztZQUM3RCxJQUFJLFFBQVEsR0FBRyxVQUFVLElBQUksVUFBVSxHQUFHLElBQUksSUFBSSx1QkFBdUIsQ0FBQztZQUMxRSxNQUFNLElBQUksT0FBTyxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUMsVUFBVSxDQUFDLE9BQU8sRUFBRSxRQUFRLENBQUMsQ0FBQyxDQUFDO1lBQzVELFNBQVM7UUFDYixDQUFDO1FBQ0QsTUFBTSxJQUFJLEtBQUssQ0FDWCx5REFBeUQsUUFBUSxDQUFDLE1BQU0sSUFBSSxRQUFRLENBQUMsVUFBVSxVQUFVLElBQUksRUFBRSxDQUNsSCxDQUFDO0lBQ04sQ0FBQztBQUNMLENBQUM7QUFFRCxTQUFTLFNBQVMsQ0FBQyxVQUFrQixFQUFFLE9BQWU7SUFDbEQsSUFBSSxJQUFJLEdBQUcsR0FBRyxFQUFFLENBQUMsYUFBYSxDQUFDLFVBQVUsRUFBRSxPQUFPLENBQUMsQ0FBQztJQUNwRCxJQUFJLE1BQU0sR0FBRyxTQUFTLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxJQUFJLENBQUMsQ0FBQztJQUN4QyxTQUFTLEdBQUcsTUFBTSxDQUFDLEtBQUssQ0FBQyxHQUFHLEVBQUUsQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUMxQyxPQUFPLE1BQU0sQ0FBQztBQUNsQixDQUFDO0FBRUQ7OEVBQzhFO0FBQzlFLFNBQWdCLGdCQUFnQixDQUFDLE1BQStDO0lBQzVFLE9BQU8sU0FBUyxDQUFDLE1BQU0sQ0FBQyxVQUFVLEVBQUUsTUFBTSxDQUFDLE9BQU8sQ0FBQyxDQUFDO0FBQ3hELENBQUM7QUFGRCw0Q0FFQztBQUVELEtBQUssVUFBVSx1QkFBdUI7SUFDbEMsSUFBSSxLQUFLLEdBQUcsaUJBQWlCLENBQUM7SUFDOUIsSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQ1QsT0FBTztJQUNYLENBQUM7SUFDRCxJQUFJLGFBQWEsQ0FBQztJQUNsQixJQUFJLENBQUM7UUFDRCxhQUFhLEdBQUcsTUFBTSxlQUFlLENBQUMsS0FBSyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQzFELENBQUM7SUFBQyxPQUFPLENBQUMsRUFBRSxDQUFDO1FBQ1QsT0FBTyxDQUFDLEtBQUssQ0FBQywyRkFBMkYsQ0FBQyxFQUFFLENBQUMsQ0FBQztRQUM5RyxPQUFPO0lBQ1gsQ0FBQztJQUNELElBQUksYUFBYSxLQUFLLEtBQUssQ0FBQyxVQUFVLEVBQUUsQ0FBQztRQUNyQyxPQUFPO0lBQ1gsQ0FBQztJQUNELDhGQUE4RjtJQUM5RixzQ0FBc0M7SUFDdEMsSUFBSSxDQUFDO1FBQ0QsTUFBTSxTQUFTLENBQ1gsS0FBSyxDQUFDLFVBQVUsRUFDaEIsNENBQTRDLEtBQUssQ0FBQyxRQUFRLGVBQWU7Y0FDdkUsTUFBTSxnQkFBZ0IsQ0FBQyxhQUFhLENBQUMsS0FBSztjQUMxQyx5RkFBeUYsQ0FDOUYsQ0FBQztJQUNOLENBQUM7SUFBQyxPQUFPLENBQUMsRUFBRSxDQUFDO1FBQ1QsT0FBTyxDQUFDLEtBQUssQ0FBQyx3RUFBd0UsQ0FBQyxFQUFFLENBQUMsQ0FBQztJQUMvRixDQUFDO0lBQ0QsS0FBSyxDQUFDLFVBQVUsR0FBRyxhQUFhLENBQUM7QUFDckMsQ0FBQztBQUVEOzttQ0FFbUM7QUFDNUIsS0FBSyxVQUFVLDZCQUE2QixDQUFDLE1BR25EO0lBQ0csSUFBSSxpQkFBaUIsRUFBRSxDQUFDO1FBQ3BCLE1BQU0sSUFBSSxLQUFLLENBQUMsNkZBQTZGLGlCQUFpQixDQUFDLFFBQVEsR0FBRyxDQUFDLENBQUM7SUFDaEosQ0FBQztJQUNELElBQUksUUFBUSxHQUFHLENBQUEsTUFBTSxhQUFOLE1BQU0sdUJBQU4sTUFBTSxDQUFFLFFBQVEsS0FBSSxpQ0FBeUIsQ0FBQztJQUM3RCxJQUFJLGFBQWEsR0FBRyxDQUFBLE1BQU0sYUFBTixNQUFNLHVCQUFOLE1BQU0sQ0FBRSxhQUFhLEtBQUksc0JBQXNCLENBQUM7SUFDcEUsSUFBSSxVQUFVLENBQUM7SUFDZixJQUFJLENBQUM7UUFDRCxVQUFVLEdBQUcsTUFBTSxlQUFlLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDakQsQ0FBQztJQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7UUFDVCxPQUFPLENBQUMsS0FBSyxDQUFDLHdFQUF3RSxDQUFDLEVBQUUsQ0FBQyxDQUFDO1FBQzNGLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDcEIsQ0FBQztJQUNELElBQUksVUFBVSxHQUFHLFdBQVcsQ0FBQyxHQUFHLEVBQUU7UUFDOUIsdUJBQXVCLEVBQUUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLGtEQUFrRCxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFDL0csQ0FBQyxFQUFFLGFBQWEsQ0FBQyxDQUFDO0lBQ2xCLGdFQUFnRTtJQUNoRSxVQUFVLENBQUMsS0FBSyxFQUFFLENBQUM7SUFDbkIsaUJBQWlCLEdBQUcsRUFBRSxRQUFRLEVBQUUsVUFBVSxFQUFFLFVBQVUsRUFBRSxDQUFDO0lBQ3pELE9BQU8sRUFBRSxRQUFRLEVBQUUsYUFBYSxFQUFFLENBQUM7QUFDdkMsQ0FBQztBQXZCRCxzRUF1QkM7QUFFRCxnR0FBZ0c7QUFDaEcsaUdBQWlHO0FBQ2pHLHVFQUF1RTtBQUNoRSxLQUFLLFVBQVUsdUJBQXVCLENBQUMsT0FBZTtJQUN6RCxJQUFJLEtBQUssR0FBRyxpQkFBaUIsQ0FBQztJQUM5QixJQUFJLENBQUMsS0FBSyxFQUFFLENBQUM7UUFDVCxNQUFNLElBQUksS0FBSyxDQUFDLCtGQUErRixPQUFPLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxxQkFBcUIsQ0FBQyxFQUFFLENBQUMsQ0FBQztJQUM5SixDQUFDO0lBQ0QsTUFBTSxTQUFTLENBQUMsS0FBSyxDQUFDLFVBQVUsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUMvQyxDQUFDO0FBTkQsMERBTUM7QUFFRCxTQUFnQix3QkFBd0I7SUFDcEMsSUFBSSxDQUFDLGlCQUFpQixFQUFFLENBQUM7UUFDckIsT0FBTztJQUNYLENBQUM7SUFDRCxhQUFhLENBQUMsaUJBQWlCLENBQUMsVUFBVSxDQUFDLENBQUM7SUFDNUMsaUJBQWlCLEdBQUcsU0FBUyxDQUFDO0FBQ2xDLENBQUM7QUFORCw0REFNQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBmcyBmcm9tIFwiZnMvcHJvbWlzZXNcIjtcblxuLy8gUE9SVEVEIENPREU6IHNlY3VyaXR5L2F1dGhvcml6ZWRLZXlzL2RhZW1vbi9wb3J0c2VjdXJlRGFlbW9uLmpzIGVtYmVkcyBhIGhhbmQgcG9ydCBvZiB0aGlzIGZpbGUgaW4gcGxhaW4gSlMsIHNvIHRoZVxuLy8gZGFlbW9uIGNhbiBydW4gd2l0aCBubyBkZXBlbmRlbmNpZXMuIFRoZSB0d28gYXJlIGV4cGVjdGVkIHRvIGJlaGF2ZSBpZGVudGljYWxseSAtIGlmIHlvdSBjaGFuZ2Vcbi8vIG9uZSwgbWFrZSB0aGUgbWF0Y2hpbmcgY2hhbmdlIGluIHRoZSBvdGhlci5cblxuZXhwb3J0IGNvbnN0IERFRkFVTFRfV0VCSE9PS19GSUxFX1BBVEggPSBcIi9ldGMvcG9ydHNlY3VyZS9kaXNjb3JkLXdlYmhvb2tcIjtcbmNvbnN0IERFRkFVTFRfQ0hFQ0tfSU5URVJWQUwgPSA1ICogNjAgKiAxMDAwO1xuY29uc3QgVkFMSURfV0VCSE9PS19QUkVGSVhFUyA9IFtcbiAgICBcImh0dHBzOi8vZGlzY29yZC5jb20vYXBpL3dlYmhvb2tzL1wiLFxuICAgIFwiaHR0cHM6Ly9kaXNjb3JkYXBwLmNvbS9hcGkvd2ViaG9va3MvXCIsXG4gICAgXCJodHRwczovL2NhbmFyeS5kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvXCIsXG5dO1xuY29uc3QgRElTQ09SRF9NRVNTQUdFX0xJTUlUID0gMjAwMDtcbmNvbnN0IE1BWF9FUlJPUl9CT0RZX0xFTkdUSCA9IDUwMDtcbmNvbnN0IE1BWF9TRU5EX0FUVEVNUFRTID0gMztcbmNvbnN0IFJFREFDVEVEX1RPS0VOX1ZJU0lCTEUgPSA4O1xuY29uc3QgREVGQVVMVF9SQVRFX0xJTUlUX1dBSVQgPSAyICogMTAwMDtcblxubGV0IG5vdGlmaWNhdGlvblN0YXRlOiB7XG4gICAgZmlsZVBhdGg6IHN0cmluZztcbiAgICB3ZWJob29rVVJMOiBzdHJpbmc7XG4gICAgY2hlY2tUaW1lcjogTm9kZUpTLlRpbWVvdXQ7XG59IHwgdW5kZWZpbmVkO1xuXG4vLyBEaXNjb3JkIHdlYmhvb2tzIGFyZSByYXRlIGxpbWl0ZWQgKHJvdWdobHkgNSByZXF1ZXN0cyBwZXIgMiBzZWNvbmRzKSwgc28gZXZlcnkgc2VuZFxuLy8gZ29lcyB0aHJvdWdoIG9uZSBjaGFpbiBpbnN0ZWFkIG9mIHJhY2luZy5cbmxldCBzZW5kQ2hhaW46IFByb21pc2U8dW5rbm93bj4gPSBQcm9taXNlLnJlc29sdmUoKTtcblxuLyoqIFB1bGxzIHRoZSB3ZWJob29rIFVSTCBvdXQgb2Ygd2ViaG9vayBmaWxlIGNvbnRlbnRzLiBgc291cmNlTmFtZWAgb25seSBhcHBlYXJzIGluIGVycm9ycywgc29cbiAgICBjYWxsZXJzIGNhbiBuYW1lIGEgcmVtb3RlIHBhdGguICovXG5leHBvcnQgZnVuY3Rpb24gcGFyc2VXZWJob29rRmlsZShjb25maWc6IHsgY29udGVudHM6IHN0cmluZzsgc291cmNlTmFtZTogc3RyaW5nIH0pIHtcbiAgICBsZXQgeyBjb250ZW50cywgc291cmNlTmFtZSB9ID0gY29uZmlnO1xuICAgIGxldCBsaW5lcyA9IGNvbnRlbnRzLnNwbGl0KFwiXFxuXCIpLm1hcChsaW5lID0+IGxpbmUudHJpbSgpKS5maWx0ZXIobGluZSA9PiBsaW5lICYmICFsaW5lLnN0YXJ0c1dpdGgoXCIjXCIpKTtcbiAgICBsZXQgd2ViaG9va1VSTCA9IGxpbmVzWzBdO1xuICAgIGlmICghd2ViaG9va1VSTCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIGEgRGlzY29yZCB3ZWJob29rIFVSTCBpbiAke3NvdXJjZU5hbWV9LCB0aGUgZmlsZSBoYXMgbm8gdXNhYmxlIGxpbmVzYCk7XG4gICAgfVxuICAgIGlmICghVkFMSURfV0VCSE9PS19QUkVGSVhFUy5zb21lKHByZWZpeCA9PiB3ZWJob29rVVJMLnN0YXJ0c1dpdGgocHJlZml4KSkpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgYEV4cGVjdGVkIGEgRGlzY29yZCB3ZWJob29rIFVSTCBzdGFydGluZyB3aXRoIG9uZSBvZiAke1ZBTElEX1dFQkhPT0tfUFJFRklYRVMuam9pbihcIiwgXCIpfSwgYFxuICAgICAgICAgICAgKyBgd2FzICR7d2ViaG9va1VSTC5zbGljZSgwLCBNQVhfRVJST1JfQk9EWV9MRU5HVEgpfSAoaW4gJHtzb3VyY2VOYW1lfSlgXG4gICAgICAgICk7XG4gICAgfVxuICAgIHJldHVybiB3ZWJob29rVVJMO1xufVxuXG4vKiogS2VlcHMgdGhlIGlkIGFuZCBib3RoIGVuZHMgb2YgdGhlIHRva2VuLCBzbyBhIHJlYWRlciBjYW4gY29uZmlybSB3aGljaCB3ZWJob29rIHJlcGxhY2VkIHRoZWlyc1xuICAgIHdpdGhvdXQgcmVjZWl2aW5nIG9uZSB0aGV5IGNvdWxkIHBvc3QgdG8uICovXG5leHBvcnQgZnVuY3Rpb24gcmVkYWN0V2ViaG9va1VSTCh3ZWJob29rVVJMOiBzdHJpbmcpIHtcbiAgICBsZXQgc2VwYXJhdG9ySW5kZXggPSB3ZWJob29rVVJMLmxhc3RJbmRleE9mKFwiL1wiKTtcbiAgICBsZXQgYmFzZSA9IHdlYmhvb2tVUkwuc2xpY2UoMCwgc2VwYXJhdG9ySW5kZXggKyAxKTtcbiAgICBsZXQgdG9rZW4gPSB3ZWJob29rVVJMLnNsaWNlKHNlcGFyYXRvckluZGV4ICsgMSk7XG4gICAgaWYgKHRva2VuLmxlbmd0aCA8PSBSRURBQ1RFRF9UT0tFTl9WSVNJQkxFICogMikge1xuICAgICAgICAvLyBUb28gc2hvcnQgdG8gc2hvdyBib3RoIGVuZHMgd2l0aG91dCBnaXZpbmcgYXdheSB0aGUgd2hvbGUgdG9rZW4uXG4gICAgICAgIHJldHVybiBgJHtiYXNlfSR7dG9rZW4uc2xpY2UoMCwgUkVEQUNURURfVE9LRU5fVklTSUJMRSl9Li4uYDtcbiAgICB9XG4gICAgcmV0dXJuIGAke2Jhc2V9JHt0b2tlbi5zbGljZSgwLCBSRURBQ1RFRF9UT0tFTl9WSVNJQkxFKX0uLi4ke3Rva2VuLnNsaWNlKC1SRURBQ1RFRF9UT0tFTl9WSVNJQkxFKX1gO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZm9ybWF0V2ViaG9va0ZpbGUod2ViaG9va1VSTDogc3RyaW5nKSB7XG4gICAgcmV0dXJuIGAjIHBvcnRzZWN1cmUgRGlzY29yZCB3ZWJob29rLiBGaXJzdCBub24tY29tbWVudCBsaW5lIGlzIHVzZWQuXFxuJHt3ZWJob29rVVJMfVxcbmA7XG59XG5cbmFzeW5jIGZ1bmN0aW9uIHJlYWRXZWJob29rRmlsZShmaWxlUGF0aDogc3RyaW5nKSB7XG4gICAgbGV0IGNvbnRlbnRzO1xuICAgIHRyeSB7XG4gICAgICAgIGNvbnRlbnRzID0gYXdhaXQgZnMucmVhZEZpbGUoZmlsZVBhdGgsIFwidXRmOFwiKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgRXhwZWN0ZWQgYSByZWFkYWJsZSBEaXNjb3JkIHdlYmhvb2sgZmlsZSBhdCAke2ZpbGVQYXRofSwgJHtlfWApO1xuICAgIH1cbiAgICByZXR1cm4gcGFyc2VXZWJob29rRmlsZSh7IGNvbnRlbnRzLCBzb3VyY2VOYW1lOiBmaWxlUGF0aCB9KTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gcG9zdFRvV2ViaG9vayh3ZWJob29rVVJMOiBzdHJpbmcsIG1lc3NhZ2U6IHN0cmluZykge1xuICAgIGxldCBjb250ZW50ID0gbWVzc2FnZTtcbiAgICBpZiAoY29udGVudC5sZW5ndGggPiBESVNDT1JEX01FU1NBR0VfTElNSVQpIHtcbiAgICAgICAgY29udGVudCA9IGNvbnRlbnQuc2xpY2UoMCwgRElTQ09SRF9NRVNTQUdFX0xJTUlUIC0gMykgKyBcIi4uLlwiO1xuICAgIH1cbiAgICBmb3IgKGxldCBhdHRlbXB0ID0gMTsgYXR0ZW1wdCA8PSBNQVhfU0VORF9BVFRFTVBUUzsgYXR0ZW1wdCsrKSB7XG4gICAgICAgIGxldCByZXNwb25zZSA9IGF3YWl0IGZldGNoKHdlYmhvb2tVUkwsIHtcbiAgICAgICAgICAgIG1ldGhvZDogXCJQT1NUXCIsXG4gICAgICAgICAgICBoZWFkZXJzOiB7IFwiQ29udGVudC1UeXBlXCI6IFwiYXBwbGljYXRpb24vanNvblwiIH0sXG4gICAgICAgICAgICBib2R5OiBKU09OLnN0cmluZ2lmeSh7IGNvbnRlbnQgfSksXG4gICAgICAgIH0pO1xuICAgICAgICBpZiAocmVzcG9uc2Uub2spIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICBsZXQgYm9keSA9IChhd2FpdCByZXNwb25zZS50ZXh0KCkpLnNsaWNlKDAsIE1BWF9FUlJPUl9CT0RZX0xFTkdUSCk7XG4gICAgICAgIGlmIChyZXNwb25zZS5zdGF0dXMgPT09IDQyOSAmJiBhdHRlbXB0IDwgTUFYX1NFTkRfQVRURU1QVFMpIHtcbiAgICAgICAgICAgIGxldCByZXRyeUFmdGVyID0gTnVtYmVyKHJlc3BvbnNlLmhlYWRlcnMuZ2V0KFwicmV0cnktYWZ0ZXJcIikpO1xuICAgICAgICAgICAgbGV0IHdhaXRUaW1lID0gcmV0cnlBZnRlciAmJiByZXRyeUFmdGVyICogMTAwMCB8fCBERUZBVUxUX1JBVEVfTElNSVRfV0FJVDtcbiAgICAgICAgICAgIGF3YWl0IG5ldyBQcm9taXNlKHJlc29sdmUgPT4gc2V0VGltZW91dChyZXNvbHZlLCB3YWl0VGltZSkpO1xuICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgYEV4cGVjdGVkIGEgMnh4IHJlc3BvbnNlIGZyb20gdGhlIERpc2NvcmQgd2ViaG9vaywgd2FzICR7cmVzcG9uc2Uuc3RhdHVzfSAke3Jlc3BvbnNlLnN0YXR1c1RleHR9LCBib2R5ICR7Ym9keX1gXG4gICAgICAgICk7XG4gICAgfVxufVxuXG5mdW5jdGlvbiBxdWV1ZVNlbmQod2ViaG9va1VSTDogc3RyaW5nLCBtZXNzYWdlOiBzdHJpbmcpIHtcbiAgICBsZXQgc2VuZCA9ICgpID0+IHBvc3RUb1dlYmhvb2sod2ViaG9va1VSTCwgbWVzc2FnZSk7XG4gICAgbGV0IHJlc3VsdCA9IHNlbmRDaGFpbi50aGVuKHNlbmQsIHNlbmQpO1xuICAgIHNlbmRDaGFpbiA9IHJlc3VsdC5jYXRjaCgoKSA9PiB1bmRlZmluZWQpO1xuICAgIHJldHVybiByZXN1bHQ7XG59XG5cbi8qKiBTZW5kcyB0byBhbiBleHBsaWNpdCB3ZWJob29rIFVSTCwgZm9yIHRvb2xpbmcgdGhhdCBhY3RzIG9uIGEgd2ViaG9vayBiZWZvcmUgKG9yIGluc3RlYWQgb2YpXG4gICAgY29uZmlndXJlRGlzY29yZE5vdGlmaWNhdGlvbnMgLSBzZXR1cCwgbWlncmF0aW9ucywgY29ubmVjdGl2aXR5IGNoZWNrcy4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzZW5kVG9XZWJob29rVVJMKGNvbmZpZzogeyB3ZWJob29rVVJMOiBzdHJpbmc7IG1lc3NhZ2U6IHN0cmluZyB9KSB7XG4gICAgcmV0dXJuIHF1ZXVlU2VuZChjb25maWcud2ViaG9va1VSTCwgY29uZmlnLm1lc3NhZ2UpO1xufVxuXG5hc3luYyBmdW5jdGlvbiBjaGVja1dlYmhvb2tGaWxlQ2hhbmdlZCgpIHtcbiAgICBsZXQgc3RhdGUgPSBub3RpZmljYXRpb25TdGF0ZTtcbiAgICBpZiAoIXN0YXRlKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgbGV0IG5ld1dlYmhvb2tVUkw7XG4gICAgdHJ5IHtcbiAgICAgICAgbmV3V2ViaG9va1VSTCA9IGF3YWl0IHJlYWRXZWJob29rRmlsZShzdGF0ZS5maWxlUGF0aCk7XG4gICAgfSBjYXRjaCAoZSkge1xuICAgICAgICBjb25zb2xlLmVycm9yKGBwb3J0c2VjdXJlOiBEaXNjb3JkIHdlYmhvb2sgZmlsZSBpcyBubyBsb25nZXIgcmVhZGFibGUsIHN0aWxsIHVzaW5nIHRoZSBsb2FkZWQgd2ViaG9vay4gJHtlfWApO1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmIChuZXdXZWJob29rVVJMID09PSBzdGF0ZS53ZWJob29rVVJMKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgLy8gV2FybiB0aGUgb2xkIGNoYW5uZWwgZmlyc3QsIHdpdGggdGhlIG5ldyB3ZWJob29rIHJlZGFjdGVkLCBzbyBhIHN0b2xlbiBvbGQgd2ViaG9vayBkb2VzIG5vdFxuICAgIC8vIGhhbmQgdGhlIGF0dGFja2VyIGEgdXNhYmxlIG5ldyBvbmUuXG4gICAgdHJ5IHtcbiAgICAgICAgYXdhaXQgcXVldWVTZW5kKFxuICAgICAgICAgICAgc3RhdGUud2ViaG9va1VSTCxcbiAgICAgICAgICAgIGAqKnBvcnRzZWN1cmUqKjogdGhlIERpc2NvcmQgd2ViaG9vayBpbiBcXGAke3N0YXRlLmZpbGVQYXRofVxcYCBjaGFuZ2VkIHRvYFxuICAgICAgICAgICAgKyBgIFxcYCR7cmVkYWN0V2ViaG9va1VSTChuZXdXZWJob29rVVJMKX1cXGAuYFxuICAgICAgICAgICAgKyBgIE5vdGlmaWNhdGlvbnMgYXJlIG1vdmluZyB0byB0aGUgbmV3IHdlYmhvb2sgYW5kIHRoaXMgY2hhbm5lbCB3aWxsIHN0b3AgcmVjZWl2aW5nIHRoZW0uYFxuICAgICAgICApO1xuICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgY29uc29sZS5lcnJvcihgcG9ydHNlY3VyZTogZmFpbGVkIHRvIHdhcm4gdGhlIG9sZCBEaXNjb3JkIHdlYmhvb2sgYWJvdXQgdGhlIGNoYW5nZS4gJHtlfWApO1xuICAgIH1cbiAgICBzdGF0ZS53ZWJob29rVVJMID0gbmV3V2ViaG9va1VSTDtcbn1cblxuLyoqIE11c3QgYmUgY2FsbGVkIG9uY2Ugb24gc3RhcnR1cCwgYmVmb3JlIGFueSBub3RpZmljYXRpb24gaXMgc2VudC4gQWJvcnRzIHRoZSBwcm9jZXNzIGlmIHRoZVxuICAgIHdlYmhvb2sgZmlsZSBpcyBtaXNzaW5nIG9yIGludmFsaWQsIHRoZW4gcmUtY2hlY2tzIHRoZSBmaWxlIG9uIGFuIGludGVydmFsIGFuZCB3YXJucyB0aGUgb2xkXG4gICAgd2ViaG9vayB3aGVuZXZlciBpdCBjaGFuZ2VzLiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNvbmZpZ3VyZURpc2NvcmROb3RpZmljYXRpb25zKGNvbmZpZz86IHtcbiAgICBmaWxlUGF0aD86IHN0cmluZztcbiAgICBjaGVja0ludGVydmFsPzogbnVtYmVyO1xufSkge1xuICAgIGlmIChub3RpZmljYXRpb25TdGF0ZSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIGNvbmZpZ3VyZURpc2NvcmROb3RpZmljYXRpb25zIHRvIGJlIGNhbGxlZCBvbmNlLCB3YXMgY2FsbGVkIGFnYWluIChhbHJlYWR5IHVzaW5nICR7bm90aWZpY2F0aW9uU3RhdGUuZmlsZVBhdGh9KWApO1xuICAgIH1cbiAgICBsZXQgZmlsZVBhdGggPSBjb25maWc/LmZpbGVQYXRoIHx8IERFRkFVTFRfV0VCSE9PS19GSUxFX1BBVEg7XG4gICAgbGV0IGNoZWNrSW50ZXJ2YWwgPSBjb25maWc/LmNoZWNrSW50ZXJ2YWwgfHwgREVGQVVMVF9DSEVDS19JTlRFUlZBTDtcbiAgICBsZXQgd2ViaG9va1VSTDtcbiAgICB0cnkge1xuICAgICAgICB3ZWJob29rVVJMID0gYXdhaXQgcmVhZFdlYmhvb2tGaWxlKGZpbGVQYXRoKTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIGNvbnNvbGUuZXJyb3IoYHBvcnRzZWN1cmU6IHJlZnVzaW5nIHRvIHN0YXJ0IHdpdGhvdXQgYSB2YWxpZCBEaXNjb3JkIHdlYmhvb2sgZmlsZS5cXG4ke2V9YCk7XG4gICAgICAgIHByb2Nlc3MuZXhpdCgxKTtcbiAgICB9XG4gICAgbGV0IGNoZWNrVGltZXIgPSBzZXRJbnRlcnZhbCgoKSA9PiB7XG4gICAgICAgIGNoZWNrV2ViaG9va0ZpbGVDaGFuZ2VkKCkuY2F0Y2goZSA9PiBjb25zb2xlLmVycm9yKGBwb3J0c2VjdXJlOiBEaXNjb3JkIHdlYmhvb2sgZmlsZSBjaGVjayBmYWlsZWQuICR7ZX1gKSk7XG4gICAgfSwgY2hlY2tJbnRlcnZhbCk7XG4gICAgLy8gVGhlIGNoZWNrIHNob3VsZCBuZXZlciBiZSB0aGUgcmVhc29uIHRoZSBwcm9jZXNzIHN0YXlzIGFsaXZlLlxuICAgIGNoZWNrVGltZXIudW5yZWYoKTtcbiAgICBub3RpZmljYXRpb25TdGF0ZSA9IHsgZmlsZVBhdGgsIHdlYmhvb2tVUkwsIGNoZWNrVGltZXIgfTtcbiAgICByZXR1cm4geyBmaWxlUGF0aCwgY2hlY2tJbnRlcnZhbCB9O1xufVxuXG4vLyBETyBOT1QgYWRkIG5ldyBjYWxscyB0byB0aGlzLiBFdmVyeSBtZXNzYWdlIGdvZXMgdG8gYSByZWFsIERpc2NvcmQgc2VydmVyIHNvbWVvbmUgcmVhZHMsIHNvIGFcbi8vIG5vdGlmaWNhdGlvbiBpcyBvbmx5IGV2ZXIgYWRkZWQgd2hlbiB0aGUgdXNlciBleHBsaWNpdGx5IGFza3MgZm9yIHRoYXQgc3BlY2lmaWMgY2FzZS4gU3RhcnR1cCxcbi8vIHN1Y2Nlc3MsIGVycm9ycywgcmV0cmllcyBhbmQgcmVjb3ZlcmllcyBhbGwgYmVsb25nIGluIGEgbG9nIGluc3RlYWQuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gc2VuZERpc2NvcmROb3RpZmljYXRpb24obWVzc2FnZTogc3RyaW5nKSB7XG4gICAgbGV0IHN0YXRlID0gbm90aWZpY2F0aW9uU3RhdGU7XG4gICAgaWYgKCFzdGF0ZSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIGNvbmZpZ3VyZURpc2NvcmROb3RpZmljYXRpb25zIHRvIGJlIGNhbGxlZCBiZWZvcmUgc2VuZGluZywgd2FzIGNhbGxlZCB3aXRoIG1lc3NhZ2UgJHttZXNzYWdlLnNsaWNlKDAsIE1BWF9FUlJPUl9CT0RZX0xFTkdUSCl9YCk7XG4gICAgfVxuICAgIGF3YWl0IHF1ZXVlU2VuZChzdGF0ZS53ZWJob29rVVJMLCBtZXNzYWdlKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHN0b3BEaXNjb3JkTm90aWZpY2F0aW9ucygpIHtcbiAgICBpZiAoIW5vdGlmaWNhdGlvblN0YXRlKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgY2xlYXJJbnRlcnZhbChub3RpZmljYXRpb25TdGF0ZS5jaGVja1RpbWVyKTtcbiAgICBub3RpZmljYXRpb25TdGF0ZSA9IHVuZGVmaW5lZDtcbn1cbiJdfQ==
|
|
180
|
+
/* _JS_SOURCE_HASH = "d5d1b1a9e262cd5b805adb2fa7d074d1a92e9a99dd226550e7f055a9766ebd19"; */
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { DEFAULT_WEBHOOK_FILE_PATH, formatWebhookFile, parseWebhookFile, redactWebhookURL, sendToWebhookURL } from "./discord";
|
|
2
|
+
import { readRemoteFile, writeRemoteFile } from "../helpers/remoteSSH";
|
|
3
|
+
|
|
4
|
+
export const REPLACE_KEYWORD = "replace";
|
|
5
|
+
const UNPARSEABLE_LABEL = "(unparseable)";
|
|
6
|
+
|
|
7
|
+
/** Puts a webhook on a remote host, refusing to clobber a different existing one unless asked.
|
|
8
|
+
Returns what happened, so callers can decide whether to keep going. */
|
|
9
|
+
export async function ensureRemoteWebhook(config: {
|
|
10
|
+
host: string;
|
|
11
|
+
webhookURL: string;
|
|
12
|
+
replace: boolean;
|
|
13
|
+
filePath?: string;
|
|
14
|
+
}) {
|
|
15
|
+
let { host, webhookURL, replace } = config;
|
|
16
|
+
let filePath = config.filePath || DEFAULT_WEBHOOK_FILE_PATH;
|
|
17
|
+
|
|
18
|
+
let replacedURL = "";
|
|
19
|
+
let existingContents = await readRemoteFile({ host, filePath });
|
|
20
|
+
if (existingContents) {
|
|
21
|
+
// A corrupt existing file still counts as a conflict, so "replace" can repair it instead
|
|
22
|
+
// of the setup dead ending on a parse error.
|
|
23
|
+
let existingURL = UNPARSEABLE_LABEL;
|
|
24
|
+
try {
|
|
25
|
+
existingURL = parseWebhookFile({ contents: existingContents, sourceName: `${host}:${filePath}` });
|
|
26
|
+
} catch (e) {
|
|
27
|
+
console.error(`${host}:${filePath} exists but could not be parsed. ${e}`);
|
|
28
|
+
}
|
|
29
|
+
if (existingURL === webhookURL) {
|
|
30
|
+
return { outcome: "unchanged" as const, filePath };
|
|
31
|
+
}
|
|
32
|
+
if (!replace) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Expected no conflicting webhook on ${host}, but ${filePath} already holds a different one.\n`
|
|
35
|
+
+ `Existing: ${existingURL}\n`
|
|
36
|
+
+ `New: ${webhookURL}\n`
|
|
37
|
+
+ `Pass "${REPLACE_KEYWORD}" to overwrite it.`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
replacedURL = existingURL;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await writeRemoteFile({
|
|
44
|
+
host,
|
|
45
|
+
filePath,
|
|
46
|
+
contents: formatWebhookFile(webhookURL),
|
|
47
|
+
fileMode: "600",
|
|
48
|
+
directoryMode: "700",
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
let writtenContents = await readRemoteFile({ host, filePath });
|
|
52
|
+
if (!writtenContents) {
|
|
53
|
+
throw new Error(`Expected ${filePath} to exist on ${host} after writing, no such file exists`);
|
|
54
|
+
}
|
|
55
|
+
let writtenURL = parseWebhookFile({ contents: writtenContents, sourceName: `${host}:${filePath}` });
|
|
56
|
+
if (writtenURL !== webhookURL) {
|
|
57
|
+
throw new Error(`Expected ${host}:${filePath} to hold the new webhook, was ${writtenURL}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (replacedURL && replacedURL !== UNPARSEABLE_LABEL) {
|
|
61
|
+
// The new webhook is redacted, so this channel can identify the replacement without
|
|
62
|
+
// receiving a webhook it could post to.
|
|
63
|
+
try {
|
|
64
|
+
await sendToWebhookURL({
|
|
65
|
+
webhookURL: replacedURL,
|
|
66
|
+
message: `**portsecure**: the Discord webhook for \`${host}\` was replaced with`
|
|
67
|
+
+ ` \`${redactWebhookURL(webhookURL)}\`.`
|
|
68
|
+
+ ` This channel will stop receiving notifications for that host.`,
|
|
69
|
+
});
|
|
70
|
+
} catch (e) {
|
|
71
|
+
// The replacement already happened, so a dead old webhook must not fail the setup.
|
|
72
|
+
console.error(`Could not notify the old webhook that it was replaced. ${e}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Failing here means the file is in place but the webhook itself does not work, which is
|
|
77
|
+
// exactly what the operator needs to hear about.
|
|
78
|
+
await sendToWebhookURL({
|
|
79
|
+
webhookURL,
|
|
80
|
+
message: `**portsecure**: notifications are now configured for \`${host}\`.`
|
|
81
|
+
+ ` This channel will receive its security notifications.`,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return { outcome: replacedURL && "replaced" as const || "created" as const, filePath };
|
|
85
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { parseWebhookFile } from "./discord";
|
|
2
|
+
import { ensureRemoteWebhook, REPLACE_KEYWORD } from "./remoteWebhook";
|
|
3
|
+
|
|
4
|
+
const USAGE = `Usage: yarn setupnotify <host> <discord-webhook-url> [${REPLACE_KEYWORD}]`;
|
|
5
|
+
|
|
6
|
+
function parseArgs(argv: string[]) {
|
|
7
|
+
let replace = argv.includes(REPLACE_KEYWORD);
|
|
8
|
+
let positional = argv.filter(arg => arg !== REPLACE_KEYWORD);
|
|
9
|
+
if (positional.length !== 2) {
|
|
10
|
+
throw new Error(`Expected a host and a webhook URL, was ${positional.length} argument(s): ${positional.join(" ") || "(none)"}\n${USAGE}`);
|
|
11
|
+
}
|
|
12
|
+
let [host, webhookURL] = positional;
|
|
13
|
+
// Validates the URL shape up front, so we never ssh anywhere with a bad webhook.
|
|
14
|
+
parseWebhookFile({ contents: webhookURL, sourceName: "the command line" });
|
|
15
|
+
return { host, webhookURL, replace };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function main() {
|
|
19
|
+
let { host, webhookURL, replace } = parseArgs(process.argv.slice(2));
|
|
20
|
+
let { outcome, filePath } = await ensureRemoteWebhook({ host, webhookURL, replace });
|
|
21
|
+
if (outcome === "unchanged") {
|
|
22
|
+
console.log(`${host} already has this exact webhook in ${filePath}, nothing to do.`);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (outcome === "replaced") {
|
|
26
|
+
console.log(`Replaced the webhook on ${host} and notified the old one.`);
|
|
27
|
+
}
|
|
28
|
+
console.log(`Wrote ${filePath} on ${host} (mode 600), and confirmed on the new webhook.`);
|
|
29
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule && mod.default) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true , configurable: true});
|
|
6
|
+
//exports.formatManifest = exports.buildManifest = exports.digestFile = exports.normalizeContent = exports.listRepoFiles = exports.MANIFEST_VERSION = exports.SIGN_NAMESPACE = exports.SIGNATURE_NAME = exports.MANIFEST_NAME = void 0;
|
|
7
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const spawn_1 = require("../helpers/spawn");
|
|
11
|
+
// PORTED CODE: security/authorizedKeys/daemon/portsecureDaemon.js contains a plain JS port of the
|
|
12
|
+
// verifying half of this file, so it can check a signature with no dependencies. Both sides must
|
|
13
|
+
// agree on the manifest shape and on which files it covers - if you change one, make the matching
|
|
14
|
+
// change in the other.
|
|
15
|
+
exports.MANIFEST_NAME = "signedfiles.json";
|
|
16
|
+
exports.SIGNATURE_NAME = "signedfiles.json.sig";
|
|
17
|
+
// ssh signatures are namespaced, so a signature made for one purpose cannot be replayed as another.
|
|
18
|
+
exports.SIGN_NAMESPACE = "signfiles";
|
|
19
|
+
exports.MANIFEST_VERSION = 1;
|
|
20
|
+
/** Everything in the repo that is not ignored, which is exactly what a clone of it will contain.
|
|
21
|
+
The manifest and its signature are left out, since they cannot describe themselves. */
|
|
22
|
+
async function listRepoFiles(repoPath) {
|
|
23
|
+
let result = await (0, spawn_1.spawnPromise)({
|
|
24
|
+
command: "git",
|
|
25
|
+
args: ["ls-files", "--cached", "--others", "--exclude-standard"],
|
|
26
|
+
cwd: repoPath,
|
|
27
|
+
});
|
|
28
|
+
if (result.status !== 0) {
|
|
29
|
+
throw new Error(`Expected to list the files in ${repoPath}, git ls-files exited ${result.status}. `
|
|
30
|
+
+ `${(result.stdout + result.stderr).trim()}`);
|
|
31
|
+
}
|
|
32
|
+
return result.stdout.split("\n")
|
|
33
|
+
.map(line => line.trim())
|
|
34
|
+
.filter(line => line && line !== exports.MANIFEST_NAME && line !== exports.SIGNATURE_NAME)
|
|
35
|
+
.sort();
|
|
36
|
+
}
|
|
37
|
+
exports.listRepoFiles = listRepoFiles;
|
|
38
|
+
/** A Windows checkout turns LF into CRLF, so the same commit hashes differently there than it
|
|
39
|
+
does on the machine that pulls it. Normalising first makes the digest describe the content
|
|
40
|
+
rather than whichever checkout produced it. latin1 round trips every byte, so this is safe on
|
|
41
|
+
files that are not text. */
|
|
42
|
+
function normalizeContent(contents) {
|
|
43
|
+
return Buffer.from(contents.toString("latin1").replace(/\r\n/g, "\n"), "latin1");
|
|
44
|
+
}
|
|
45
|
+
exports.normalizeContent = normalizeContent;
|
|
46
|
+
/** The size and hash of a file's normalised content. The size is the normalised one on purpose,
|
|
47
|
+
so it agrees with the hash rather than with whatever the local checkout happens to hold. */
|
|
48
|
+
async function digestFile(filePath) {
|
|
49
|
+
let contents = normalizeContent(await promises_1.default.readFile(filePath));
|
|
50
|
+
return { size: contents.length, sha256: crypto_1.default.createHash("sha256").update(contents).digest("hex") };
|
|
51
|
+
}
|
|
52
|
+
exports.digestFile = digestFile;
|
|
53
|
+
async function buildManifest(repoPath) {
|
|
54
|
+
let files = [];
|
|
55
|
+
for (let relativePath of await listRepoFiles(repoPath)) {
|
|
56
|
+
let digest = await digestFile(path_1.default.join(repoPath, relativePath));
|
|
57
|
+
files.push({ path: relativePath, size: digest.size, sha256: digest.sha256 });
|
|
58
|
+
}
|
|
59
|
+
return { version: exports.MANIFEST_VERSION, files };
|
|
60
|
+
}
|
|
61
|
+
exports.buildManifest = buildManifest;
|
|
62
|
+
/** Sorted keys and a trailing newline, so the same tree always produces the same bytes to sign. */
|
|
63
|
+
function formatManifest(manifest) {
|
|
64
|
+
return JSON.stringify(manifest, undefined, 4) + "\n";
|
|
65
|
+
}
|
|
66
|
+
exports.formatManifest = formatManifest;
|
|
67
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFuaWZlc3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJtYW5pZmVzdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7QUFBQSxvREFBNEI7QUFDNUIsMkRBQTZCO0FBQzdCLGdEQUF3QjtBQUN4Qiw0Q0FBZ0Q7QUFFaEQsa0dBQWtHO0FBQ2xHLGlHQUFpRztBQUNqRyxrR0FBa0c7QUFDbEcsdUJBQXVCO0FBRVYsUUFBQSxhQUFhLEdBQUcsa0JBQWtCLENBQUM7QUFDbkMsUUFBQSxjQUFjLEdBQUcsc0JBQXNCLENBQUM7QUFDckQsb0dBQW9HO0FBQ3ZGLFFBQUEsY0FBYyxHQUFHLFdBQVcsQ0FBQztBQUM3QixRQUFBLGdCQUFnQixHQUFHLENBQUMsQ0FBQztBQU9sQzswRkFDMEY7QUFDbkYsS0FBSyxVQUFVLGFBQWEsQ0FBQyxRQUFnQjtJQUNoRCxJQUFJLE1BQU0sR0FBRyxNQUFNLElBQUEsb0JBQVksRUFBQztRQUM1QixPQUFPLEVBQUUsS0FBSztRQUNkLElBQUksRUFBRSxDQUFDLFVBQVUsRUFBRSxVQUFVLEVBQUUsVUFBVSxFQUFFLG9CQUFvQixDQUFDO1FBQ2hFLEdBQUcsRUFBRSxRQUFRO0tBQ2hCLENBQUMsQ0FBQztJQUNILElBQUksTUFBTSxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztRQUN0QixNQUFNLElBQUksS0FBSyxDQUNYLGlDQUFpQyxRQUFRLHlCQUF5QixNQUFNLENBQUMsTUFBTSxJQUFJO2NBQ2pGLEdBQUcsQ0FBQyxNQUFNLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUNoRCxDQUFDO0lBQ04sQ0FBQztJQUNELE9BQU8sTUFBTSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDO1NBQzNCLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztTQUN4QixNQUFNLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLElBQUksSUFBSSxLQUFLLHFCQUFhLElBQUksSUFBSSxLQUFLLHNCQUFjLENBQUM7U0FDekUsSUFBSSxFQUFFLENBQUM7QUFDaEIsQ0FBQztBQWhCRCxzQ0FnQkM7QUFFRDs7OytCQUcrQjtBQUMvQixTQUFnQixnQkFBZ0IsQ0FBQyxRQUFnQjtJQUM3QyxPQUFPLE1BQU0sQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUMsQ0FBQyxPQUFPLENBQUMsT0FBTyxFQUFFLElBQUksQ0FBQyxFQUFFLFFBQVEsQ0FBQyxDQUFDO0FBQ3JGLENBQUM7QUFGRCw0Q0FFQztBQUVEOytGQUMrRjtBQUN4RixLQUFLLFVBQVUsVUFBVSxDQUFDLFFBQWdCO0lBQzdDLElBQUksUUFBUSxHQUFHLGdCQUFnQixDQUFDLE1BQU0sa0JBQUUsQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQztJQUM3RCxPQUFPLEVBQUUsSUFBSSxFQUFFLFFBQVEsQ0FBQyxNQUFNLEVBQUUsTUFBTSxFQUFFLGdCQUFNLENBQUMsVUFBVSxDQUFDLFFBQVEsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQztBQUN6RyxDQUFDO0FBSEQsZ0NBR0M7QUFFTSxLQUFLLFVBQVUsYUFBYSxDQUFDLFFBQWdCO0lBQ2hELElBQUksS0FBSyxHQUFzQixFQUFFLENBQUM7SUFDbEMsS0FBSyxJQUFJLFlBQVksSUFBSSxNQUFNLGFBQWEsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDO1FBQ3JELElBQUksTUFBTSxHQUFHLE1BQU0sVUFBVSxDQUFDLGNBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLFlBQVksQ0FBQyxDQUFDLENBQUM7UUFDakUsS0FBSyxDQUFDLElBQUksQ0FBQyxFQUFFLElBQUksRUFBRSxZQUFZLEVBQUUsSUFBSSxFQUFFLE1BQU0sQ0FBQyxJQUFJLEVBQUUsTUFBTSxFQUFFLE1BQU0sQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDO0lBQ2pGLENBQUM7SUFDRCxPQUFPLEVBQUUsT0FBTyxFQUFFLHdCQUFnQixFQUFFLEtBQUssRUFBRSxDQUFDO0FBQ2hELENBQUM7QUFQRCxzQ0FPQztBQUVELG1HQUFtRztBQUNuRyxTQUFnQixjQUFjLENBQUMsUUFBa0I7SUFDN0MsT0FBTyxJQUFJLENBQUMsU0FBUyxDQUFDLFFBQVEsRUFBRSxTQUFTLEVBQUUsQ0FBQyxDQUFDLEdBQUcsSUFBSSxDQUFDO0FBQ3pELENBQUM7QUFGRCx3Q0FFQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBjcnlwdG8gZnJvbSBcImNyeXB0b1wiO1xuaW1wb3J0IGZzIGZyb20gXCJmcy9wcm9taXNlc1wiO1xuaW1wb3J0IHBhdGggZnJvbSBcInBhdGhcIjtcbmltcG9ydCB7IHNwYXduUHJvbWlzZSB9IGZyb20gXCIuLi9oZWxwZXJzL3NwYXduXCI7XG5cbi8vIFBPUlRFRCBDT0RFOiBzZWN1cml0eS9hdXRob3JpemVkS2V5cy9kYWVtb24vcG9ydHNlY3VyZURhZW1vbi5qcyBjb250YWlucyBhIHBsYWluIEpTIHBvcnQgb2YgdGhlXG4vLyB2ZXJpZnlpbmcgaGFsZiBvZiB0aGlzIGZpbGUsIHNvIGl0IGNhbiBjaGVjayBhIHNpZ25hdHVyZSB3aXRoIG5vIGRlcGVuZGVuY2llcy4gQm90aCBzaWRlcyBtdXN0XG4vLyBhZ3JlZSBvbiB0aGUgbWFuaWZlc3Qgc2hhcGUgYW5kIG9uIHdoaWNoIGZpbGVzIGl0IGNvdmVycyAtIGlmIHlvdSBjaGFuZ2Ugb25lLCBtYWtlIHRoZSBtYXRjaGluZ1xuLy8gY2hhbmdlIGluIHRoZSBvdGhlci5cblxuZXhwb3J0IGNvbnN0IE1BTklGRVNUX05BTUUgPSBcInNpZ25lZGZpbGVzLmpzb25cIjtcbmV4cG9ydCBjb25zdCBTSUdOQVRVUkVfTkFNRSA9IFwic2lnbmVkZmlsZXMuanNvbi5zaWdcIjtcbi8vIHNzaCBzaWduYXR1cmVzIGFyZSBuYW1lc3BhY2VkLCBzbyBhIHNpZ25hdHVyZSBtYWRlIGZvciBvbmUgcHVycG9zZSBjYW5ub3QgYmUgcmVwbGF5ZWQgYXMgYW5vdGhlci5cbmV4cG9ydCBjb25zdCBTSUdOX05BTUVTUEFDRSA9IFwic2lnbmZpbGVzXCI7XG5leHBvcnQgY29uc3QgTUFOSUZFU1RfVkVSU0lPTiA9IDE7XG5cbmV4cG9ydCB0eXBlIE1hbmlmZXN0ID0ge1xuICAgIHZlcnNpb246IG51bWJlcjtcbiAgICBmaWxlczogeyBwYXRoOiBzdHJpbmc7IHNpemU6IG51bWJlcjsgc2hhMjU2OiBzdHJpbmcgfVtdO1xufTtcblxuLyoqIEV2ZXJ5dGhpbmcgaW4gdGhlIHJlcG8gdGhhdCBpcyBub3QgaWdub3JlZCwgd2hpY2ggaXMgZXhhY3RseSB3aGF0IGEgY2xvbmUgb2YgaXQgd2lsbCBjb250YWluLlxuICAgIFRoZSBtYW5pZmVzdCBhbmQgaXRzIHNpZ25hdHVyZSBhcmUgbGVmdCBvdXQsIHNpbmNlIHRoZXkgY2Fubm90IGRlc2NyaWJlIHRoZW1zZWx2ZXMuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gbGlzdFJlcG9GaWxlcyhyZXBvUGF0aDogc3RyaW5nKSB7XG4gICAgbGV0IHJlc3VsdCA9IGF3YWl0IHNwYXduUHJvbWlzZSh7XG4gICAgICAgIGNvbW1hbmQ6IFwiZ2l0XCIsXG4gICAgICAgIGFyZ3M6IFtcImxzLWZpbGVzXCIsIFwiLS1jYWNoZWRcIiwgXCItLW90aGVyc1wiLCBcIi0tZXhjbHVkZS1zdGFuZGFyZFwiXSxcbiAgICAgICAgY3dkOiByZXBvUGF0aCxcbiAgICB9KTtcbiAgICBpZiAocmVzdWx0LnN0YXR1cyAhPT0gMCkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICBgRXhwZWN0ZWQgdG8gbGlzdCB0aGUgZmlsZXMgaW4gJHtyZXBvUGF0aH0sIGdpdCBscy1maWxlcyBleGl0ZWQgJHtyZXN1bHQuc3RhdHVzfS4gYFxuICAgICAgICAgICAgKyBgJHsocmVzdWx0LnN0ZG91dCArIHJlc3VsdC5zdGRlcnIpLnRyaW0oKX1gXG4gICAgICAgICk7XG4gICAgfVxuICAgIHJldHVybiByZXN1bHQuc3Rkb3V0LnNwbGl0KFwiXFxuXCIpXG4gICAgICAgIC5tYXAobGluZSA9PiBsaW5lLnRyaW0oKSlcbiAgICAgICAgLmZpbHRlcihsaW5lID0+IGxpbmUgJiYgbGluZSAhPT0gTUFOSUZFU1RfTkFNRSAmJiBsaW5lICE9PSBTSUdOQVRVUkVfTkFNRSlcbiAgICAgICAgLnNvcnQoKTtcbn1cblxuLyoqIEEgV2luZG93cyBjaGVja291dCB0dXJucyBMRiBpbnRvIENSTEYsIHNvIHRoZSBzYW1lIGNvbW1pdCBoYXNoZXMgZGlmZmVyZW50bHkgdGhlcmUgdGhhbiBpdFxuICAgIGRvZXMgb24gdGhlIG1hY2hpbmUgdGhhdCBwdWxscyBpdC4gTm9ybWFsaXNpbmcgZmlyc3QgbWFrZXMgdGhlIGRpZ2VzdCBkZXNjcmliZSB0aGUgY29udGVudFxuICAgIHJhdGhlciB0aGFuIHdoaWNoZXZlciBjaGVja291dCBwcm9kdWNlZCBpdC4gbGF0aW4xIHJvdW5kIHRyaXBzIGV2ZXJ5IGJ5dGUsIHNvIHRoaXMgaXMgc2FmZSBvblxuICAgIGZpbGVzIHRoYXQgYXJlIG5vdCB0ZXh0LiAqL1xuZXhwb3J0IGZ1bmN0aW9uIG5vcm1hbGl6ZUNvbnRlbnQoY29udGVudHM6IEJ1ZmZlcikge1xuICAgIHJldHVybiBCdWZmZXIuZnJvbShjb250ZW50cy50b1N0cmluZyhcImxhdGluMVwiKS5yZXBsYWNlKC9cXHJcXG4vZywgXCJcXG5cIiksIFwibGF0aW4xXCIpO1xufVxuXG4vKiogVGhlIHNpemUgYW5kIGhhc2ggb2YgYSBmaWxlJ3Mgbm9ybWFsaXNlZCBjb250ZW50LiBUaGUgc2l6ZSBpcyB0aGUgbm9ybWFsaXNlZCBvbmUgb24gcHVycG9zZSxcbiAgICBzbyBpdCBhZ3JlZXMgd2l0aCB0aGUgaGFzaCByYXRoZXIgdGhhbiB3aXRoIHdoYXRldmVyIHRoZSBsb2NhbCBjaGVja291dCBoYXBwZW5zIHRvIGhvbGQuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZGlnZXN0RmlsZShmaWxlUGF0aDogc3RyaW5nKSB7XG4gICAgbGV0IGNvbnRlbnRzID0gbm9ybWFsaXplQ29udGVudChhd2FpdCBmcy5yZWFkRmlsZShmaWxlUGF0aCkpO1xuICAgIHJldHVybiB7IHNpemU6IGNvbnRlbnRzLmxlbmd0aCwgc2hhMjU2OiBjcnlwdG8uY3JlYXRlSGFzaChcInNoYTI1NlwiKS51cGRhdGUoY29udGVudHMpLmRpZ2VzdChcImhleFwiKSB9O1xufVxuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gYnVpbGRNYW5pZmVzdChyZXBvUGF0aDogc3RyaW5nKSB7XG4gICAgbGV0IGZpbGVzOiBNYW5pZmVzdFtcImZpbGVzXCJdID0gW107XG4gICAgZm9yIChsZXQgcmVsYXRpdmVQYXRoIG9mIGF3YWl0IGxpc3RSZXBvRmlsZXMocmVwb1BhdGgpKSB7XG4gICAgICAgIGxldCBkaWdlc3QgPSBhd2FpdCBkaWdlc3RGaWxlKHBhdGguam9pbihyZXBvUGF0aCwgcmVsYXRpdmVQYXRoKSk7XG4gICAgICAgIGZpbGVzLnB1c2goeyBwYXRoOiByZWxhdGl2ZVBhdGgsIHNpemU6IGRpZ2VzdC5zaXplLCBzaGEyNTY6IGRpZ2VzdC5zaGEyNTYgfSk7XG4gICAgfVxuICAgIHJldHVybiB7IHZlcnNpb246IE1BTklGRVNUX1ZFUlNJT04sIGZpbGVzIH07XG59XG5cbi8qKiBTb3J0ZWQga2V5cyBhbmQgYSB0cmFpbGluZyBuZXdsaW5lLCBzbyB0aGUgc2FtZSB0cmVlIGFsd2F5cyBwcm9kdWNlcyB0aGUgc2FtZSBieXRlcyB0byBzaWduLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdE1hbmlmZXN0KG1hbmlmZXN0OiBNYW5pZmVzdCkge1xuICAgIHJldHVybiBKU09OLnN0cmluZ2lmeShtYW5pZmVzdCwgdW5kZWZpbmVkLCA0KSArIFwiXFxuXCI7XG59XG4iXX0=
|
|
68
|
+
/* _JS_SOURCE_HASH = "b03b98c478c3d40c11ab30a9be0a3a73ddfbb41e74230864ca940f9edd07dc68"; */
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule && mod.default) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true , configurable: true});
|
|
6
|
+
//exports.main = void 0;
|
|
7
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
|
+
const os_1 = __importDefault(require("os"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const runPromise_1 = require("socket-function/src/runPromise");
|
|
11
|
+
const paths_1 = require("../helpers/paths");
|
|
12
|
+
const manifest_1 = require("./manifest");
|
|
13
|
+
const unrevoke_1 = require("../authorizedKeys/unrevoke");
|
|
14
|
+
// A hardware backed key is the entire point. A key sitting on disk is compromised the moment the
|
|
15
|
+
// machine is, and then the signature proves nothing, so this is what we make when asked to make one.
|
|
16
|
+
const DEFAULT_KEY_TYPE = "ed25519-sk";
|
|
17
|
+
const DEFAULT_KEY_PATH = "~/.ssh/signfiles_ed25519_sk";
|
|
18
|
+
const GIT_KEYWORD = "git";
|
|
19
|
+
const COMMIT_MESSAGE = "deploying signed files";
|
|
20
|
+
const MAX_ERROR_BODY_LENGTH = 500;
|
|
21
|
+
const USAGE = `Usage: yarn signfiles [signing-key] [${GIT_KEYWORD}]
|
|
22
|
+
|
|
23
|
+
Signs the files of the repo in the current directory. With no key, a hardware backed
|
|
24
|
+
${DEFAULT_KEY_TYPE} key at ${DEFAULT_KEY_PATH} is used, and created if it does not exist.
|
|
25
|
+
Pass ${GIT_KEYWORD} to also commit and push the result.`;
|
|
26
|
+
/** runPromise takes a command line rather than an argument list, so anything holding a path has to
|
|
27
|
+
survive the shell. Double quotes work on both cmd and posix shells. */
|
|
28
|
+
function quote(value) {
|
|
29
|
+
return `"${value}"`;
|
|
30
|
+
}
|
|
31
|
+
async function pathExists(filePath) {
|
|
32
|
+
try {
|
|
33
|
+
await promises_1.default.access(filePath);
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Creating this needs the security key plugged in, and a touch. */
|
|
41
|
+
async function ensureDefaultKey() {
|
|
42
|
+
let keyPath = (0, paths_1.expandHome)(DEFAULT_KEY_PATH);
|
|
43
|
+
if (await pathExists(keyPath)) {
|
|
44
|
+
return keyPath;
|
|
45
|
+
}
|
|
46
|
+
console.log(`No signing key at ${keyPath}, creating an ${DEFAULT_KEY_TYPE} one.`);
|
|
47
|
+
console.log(`Plug your security key in - you will be asked to touch it.`);
|
|
48
|
+
await promises_1.default.mkdir(path_1.default.dirname(keyPath), { recursive: true, mode: 0o700 });
|
|
49
|
+
await (0, runPromise_1.runPromise)(`ssh-keygen -t ${DEFAULT_KEY_TYPE} -f ${quote(keyPath)} -N "" -C signfiles`);
|
|
50
|
+
return keyPath;
|
|
51
|
+
}
|
|
52
|
+
/** The public key in the form the daemon reports it, so what is printed here can be compared
|
|
53
|
+
against what arrives on Discord. */
|
|
54
|
+
async function publicKeyOf(keyPath) {
|
|
55
|
+
let contents = await promises_1.default.readFile(`${keyPath}.pub`, "utf8");
|
|
56
|
+
let [keyType, keyBody] = contents.trim().split(/\s+/);
|
|
57
|
+
if (!keyType || !keyBody) {
|
|
58
|
+
throw new Error(`Expected a public key in ${keyPath}.pub, was ${contents.slice(0, MAX_ERROR_BODY_LENGTH)}`);
|
|
59
|
+
}
|
|
60
|
+
return `${keyType} ${keyBody}`;
|
|
61
|
+
}
|
|
62
|
+
/** Signing a keys repo that still holds a revoked key would publish it back to every machine that
|
|
63
|
+
took it out. Only applies to a repo that holds keys and has a revoke repo to check - signing
|
|
64
|
+
anything else is none of this function's business. */
|
|
65
|
+
async function refuseRevokedKeys(repoPath) {
|
|
66
|
+
if (!await pathExists(path_1.default.join(repoPath, "authorized_keys"))) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
let originURL = (await (0, runPromise_1.runPromise)("git remote get-url origin", { cwd: repoPath, quiet: true, nothrow: true })).trim();
|
|
70
|
+
if (!originURL) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
let revoked;
|
|
74
|
+
try {
|
|
75
|
+
revoked = await (0, unrevoke_1.revokedKeysInRepo)({ repoPath, sourceURL: originURL });
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
// No revoke repo, or no access to it. Nothing has been revoked that we can see.
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (!revoked.length) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
throw new Error(`Expected ${repoPath} to hold no revoked keys, it holds ${revoked.length}:\n`
|
|
85
|
+
+ revoked.map(entry => {
|
|
86
|
+
var _a;
|
|
87
|
+
return ` ${entry.revocation.fingerprint} revoked by ${entry.revocation.revokedBy || "?"}`
|
|
88
|
+
+ ` after use from ${((_a = entry.revocation.attempt) === null || _a === void 0 ? void 0 : _a.ip) || "?"}`;
|
|
89
|
+
}).join("\n")
|
|
90
|
+
+ `\nDelete them from authorized_keys, or run "yarn unrevoke" here to allow them again.`);
|
|
91
|
+
}
|
|
92
|
+
function parseArgs(argv) {
|
|
93
|
+
let pushToGit = argv.includes(GIT_KEYWORD);
|
|
94
|
+
let positional = argv.filter(arg => arg !== GIT_KEYWORD);
|
|
95
|
+
if (positional.length > 1) {
|
|
96
|
+
throw new Error(`Expected at most a signing key, was ${positional.length} argument(s): ${positional.join(" ")}\n${USAGE}`);
|
|
97
|
+
}
|
|
98
|
+
return { keyPath: positional[0], pushToGit };
|
|
99
|
+
}
|
|
100
|
+
async function main() {
|
|
101
|
+
let { keyPath, pushToGit } = parseArgs(process.argv.slice(2));
|
|
102
|
+
let repoPath = (await (0, runPromise_1.runPromise)("git rev-parse --show-toplevel", { quiet: true })).trim();
|
|
103
|
+
if (!repoPath) {
|
|
104
|
+
throw new Error(`Expected the current directory to be inside a git repo, it is not.\n${USAGE}`);
|
|
105
|
+
}
|
|
106
|
+
let signingKey = keyPath && (0, paths_1.expandHome)(keyPath) || await ensureDefaultKey();
|
|
107
|
+
if (!await pathExists(signingKey)) {
|
|
108
|
+
throw new Error(`Expected a signing key at ${signingKey}, no such file exists`);
|
|
109
|
+
}
|
|
110
|
+
await refuseRevokedKeys(repoPath);
|
|
111
|
+
let manifest = await (0, manifest_1.buildManifest)(repoPath);
|
|
112
|
+
console.log(`${manifest_1.MANIFEST_NAME} covers ${manifest.files.length} file(s) in ${repoPath}`);
|
|
113
|
+
// The manifest is built and signed away from the repo, and only moved in once both exist.
|
|
114
|
+
// Landing a new manifest next to an old signature produces a pair that can never verify, and
|
|
115
|
+
// the daemon reading it can only treat that as tampering.
|
|
116
|
+
let stagingDirectory = await promises_1.default.mkdtemp(path_1.default.join(os_1.default.tmpdir(), "signfiles-"));
|
|
117
|
+
let stagedManifest = path_1.default.join(stagingDirectory, manifest_1.MANIFEST_NAME);
|
|
118
|
+
let stagedSignature = `${stagedManifest}.sig`;
|
|
119
|
+
await promises_1.default.writeFile(stagedManifest, (0, manifest_1.formatManifest)(manifest));
|
|
120
|
+
// Signing happens before any git work, so a failed push never costs a second touch of the key.
|
|
121
|
+
await (0, runPromise_1.runPromise)(`ssh-keygen -Y sign -f ${quote(signingKey)} -n ${manifest_1.SIGN_NAMESPACE} ${quote(stagedManifest)}`);
|
|
122
|
+
// Checked here rather than left for a machine that has already pulled it to discover.
|
|
123
|
+
await (0, runPromise_1.runPromise)(`ssh-keygen -Y check-novalidate -n ${manifest_1.SIGN_NAMESPACE} -s ${quote(stagedSignature)} < ${quote(stagedManifest)}`);
|
|
124
|
+
await promises_1.default.copyFile(stagedManifest, path_1.default.join(repoPath, manifest_1.MANIFEST_NAME));
|
|
125
|
+
await promises_1.default.copyFile(stagedSignature, path_1.default.join(repoPath, manifest_1.SIGNATURE_NAME));
|
|
126
|
+
await promises_1.default.rm(stagingDirectory, { recursive: true, force: true });
|
|
127
|
+
console.log(`Signed with ${await publicKeyOf(signingKey)}`);
|
|
128
|
+
if (!pushToGit) {
|
|
129
|
+
console.log(`Commit and push ${manifest_1.MANIFEST_NAME} and ${manifest_1.SIGNATURE_NAME} for anything to see them.`);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
await (0, runPromise_1.runPromise)(`git add -A`, { cwd: repoPath });
|
|
133
|
+
// Nothing staged is not worth stopping on. git commit calls that a failure, but it only means
|
|
134
|
+
// the signature matches the one already committed, so there is nothing to deploy.
|
|
135
|
+
let staged = await (0, runPromise_1.runPromise)(`git status --porcelain`, { cwd: repoPath, quiet: true });
|
|
136
|
+
if (!staged.trim()) {
|
|
137
|
+
console.log(`Nothing changed, ${manifest_1.MANIFEST_NAME} and ${manifest_1.SIGNATURE_NAME} are already committed.`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
await (0, runPromise_1.runPromise)(`git commit -m ${quote(COMMIT_MESSAGE)}`, { cwd: repoPath });
|
|
141
|
+
await (0, runPromise_1.runPromise)(`git push`, { cwd: repoPath });
|
|
142
|
+
console.log(`Committed and pushed.`);
|
|
143
|
+
}
|
|
144
|
+
exports.main = main;
|
|
145
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2lnbkZpbGVzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsic2lnbkZpbGVzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7OztBQUFBLDJEQUE2QjtBQUM3Qiw0Q0FBb0I7QUFDcEIsZ0RBQXdCO0FBQ3hCLCtEQUE0RDtBQUM1RCw0Q0FBOEM7QUFDOUMseUNBQTBHO0FBQzFHLHlEQUErRDtBQUUvRCxpR0FBaUc7QUFDakcscUdBQXFHO0FBQ3JHLE1BQU0sZ0JBQWdCLEdBQUcsWUFBWSxDQUFDO0FBQ3RDLE1BQU0sZ0JBQWdCLEdBQUcsNkJBQTZCLENBQUM7QUFDdkQsTUFBTSxXQUFXLEdBQUcsS0FBSyxDQUFDO0FBQzFCLE1BQU0sY0FBYyxHQUFHLHdCQUF3QixDQUFDO0FBQ2hELE1BQU0scUJBQXFCLEdBQUcsR0FBRyxDQUFDO0FBQ2xDLE1BQU0sS0FBSyxHQUFHLHdDQUF3QyxXQUFXOzs7RUFHL0QsZ0JBQWdCLFdBQVcsZ0JBQWdCO09BQ3RDLFdBQVcsc0NBQXNDLENBQUM7QUFFekQ7MEVBQzBFO0FBQzFFLFNBQVMsS0FBSyxDQUFDLEtBQWE7SUFDeEIsT0FBTyxJQUFJLEtBQUssR0FBRyxDQUFDO0FBQ3hCLENBQUM7QUFFRCxLQUFLLFVBQVUsVUFBVSxDQUFDLFFBQWdCO0lBQ3RDLElBQUksQ0FBQztRQUNELE1BQU0sa0JBQUUsQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDMUIsT0FBTyxJQUFJLENBQUM7SUFDaEIsQ0FBQztJQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7UUFDVCxPQUFPLEtBQUssQ0FBQztJQUNqQixDQUFDO0FBQ0wsQ0FBQztBQUVELG9FQUFvRTtBQUNwRSxLQUFLLFVBQVUsZ0JBQWdCO0lBQzNCLElBQUksT0FBTyxHQUFHLElBQUEsa0JBQVUsRUFBQyxnQkFBZ0IsQ0FBQyxDQUFDO0lBQzNDLElBQUksTUFBTSxVQUFVLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztRQUM1QixPQUFPLE9BQU8sQ0FBQztJ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cG9ydCBmcyBmcm9tIFwiZnMvcHJvbWlzZXNcIjtcbmltcG9ydCBvcyBmcm9tIFwib3NcIjtcbmltcG9ydCBwYXRoIGZyb20gXCJwYXRoXCI7XG5pbXBvcnQgeyBydW5Qcm9taXNlIH0gZnJvbSBcInNvY2tldC1mdW5jdGlvbi9zcmMvcnVuUHJvbWlzZVwiO1xuaW1wb3J0IHsgZXhwYW5kSG9tZSB9IGZyb20gXCIuLi9oZWxwZXJzL3BhdGhzXCI7XG5pbXBvcnQgeyBidWlsZE1hbmlmZXN0LCBmb3JtYXRNYW5pZmVzdCwgTUFOSUZFU1RfTkFNRSwgU0lHTkFUVVJFX05BTUUsIFNJR05fTkFNRVNQQUNFIH0gZnJvbSBcIi4vbWFuaWZlc3RcIjtcbmltcG9ydCB7IHJldm9rZWRLZXlzSW5SZXBvIH0gZnJvbSBcIi4uL2F1dGhvcml6ZWRLZXlzL3VucmV2b2tlXCI7XG5cbi8vIEEgaGFyZHdhcmUgYmFja2VkIGtleSBpcyB0aGUgZW50aXJlIHBvaW50LiBBIGtleSBzaXR0aW5nIG9uIGRpc2sgaXMgY29tcHJvbWlzZWQgdGhlIG1vbWVudCB0aGVcbi8vIG1hY2hpbmUgaXMsIGFuZCB0aGVuIHRoZSBzaWduYXR1cmUgcHJvdmVzIG5vdGhpbmcsIHNvIHRoaXMgaXMgd2hhdCB3ZSBtYWtlIHdoZW4gYXNrZWQgdG8gbWFrZSBvbmUuXG5jb25zdCBERUZBVUxUX0tFWV9UWVBFID0gXCJlZDI1NTE5LXNrXCI7XG5jb25zdCBERUZBVUxUX0tFWV9QQVRIID0gXCJ+Ly5zc2gvc2lnbmZpbGVzX2VkMjU1MTlfc2tcIjtcbmNvbnN0IEdJVF9LRVlXT1JEID0gXCJnaXRcIjtcbmNvbnN0IENPTU1JVF9NRVNTQUdFID0gXCJkZXBsb3lpbmcgc2lnbmVkIGZpbGVzXCI7XG5jb25zdCBNQVhfRVJST1JfQk9EWV9MRU5HVEggPSA1MDA7XG5jb25zdCBVU0FHRSA9IGBVc2FnZTogeWFybiBzaWduZmlsZXMgW3NpZ25pbmcta2V5XSBbJHtHSVRfS0VZV09SRH1dXG5cblNpZ25zIHRoZSBmaWxlcyBvZiB0aGUgcmVwbyBpbiB0aGUgY3VycmVudCBkaXJlY3RvcnkuIFdpdGggbm8ga2V5LCBhIGhhcmR3YXJlIGJhY2tlZFxuJHtERUZBVUxUX0tFWV9UWVBFfSBrZXkgYXQgJHtERUZBVUxUX0tFWV9QQVRIfSBpcyB1c2VkLCBhbmQgY3JlYXRlZCBpZiBpdCBkb2VzIG5vdCBleGlzdC5cblBhc3MgJHtHSVRfS0VZV09SRH0gdG8gYWxzbyBjb21taXQgYW5kIHB1c2ggdGhlIHJlc3VsdC5gO1xuXG4vKiogcnVuUHJvbWlzZSB0YWtlcyBhIGNvbW1hbmQgbGluZSByYXRoZXIgdGhhbiBhbiBhcmd1bWVudCBsaXN0LCBzbyBhbnl0aGluZyBob2xkaW5nIGEgcGF0aCBoYXMgdG9cbiAgICBzdXJ2aXZlIHRoZSBzaGVsbC4gRG91YmxlIHF1b3RlcyB3b3JrIG9uIGJvdGggY21kIGFuZCBwb3NpeCBzaGVsbHMuICovXG5mdW5jdGlvbiBxdW90ZSh2YWx1ZTogc3RyaW5nKSB7XG4gICAgcmV0dXJuIGBcIiR7dmFsdWV9XCJgO1xufVxuXG5hc3luYyBmdW5jdGlvbiBwYXRoRXhpc3RzKGZpbGVQYXRoOiBzdHJpbmcpIHtcbiAgICB0cnkge1xuICAgICAgICBhd2FpdCBmcy5hY2Nlc3MoZmlsZVBhdGgpO1xuICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9XG59XG5cbi8qKiBDcmVhdGluZyB0aGlzIG5lZWRzIHRoZSBzZWN1cml0eSBrZXkgcGx1Z2dlZCBpbiwgYW5kIGEgdG91Y2guICovXG5hc3luYyBmdW5jdGlvbiBlbnN1cmVEZWZhdWx0S2V5KCkge1xuICAgIGxldCBrZXlQYXRoID0gZXhwYW5kSG9tZShERUZBVUxUX0tFWV9QQVRIKTtcbiAgICBpZiAoYXdhaXQgcGF0aEV4aXN0cyhrZXlQYXRoKSkge1xuICAgICAgICByZXR1cm4ga2V5UGF0aDtcbiAgICB9XG4gICAgY29uc29sZS5sb2coYE5vIHNpZ25pbmcga2V5IGF0ICR7a2V5UGF0aH0sIGNyZWF0aW5nIGFuICR7REVGQVVMVF9LRVlfVFlQRX0gb25lLmApO1xuICAgIGNvbnNvbGUubG9nKGBQbHVnIHlvdXIgc2VjdXJpdHkga2V5IGluIC0geW91IHdpbGwgYmUgYXNrZWQgdG8gdG91Y2ggaXQuYCk7XG4gICAgYXdhaXQgZnMubWtkaXIocGF0aC5kaXJuYW1lKGtleVBhdGgpLCB7IHJlY3Vyc2l2ZTogdHJ1ZSwgbW9kZTogMG83MDAgfSk7XG4gICAgYXdhaXQgcnVuUHJvbWlzZShgc3NoLWtleWdlbiAtdCAke0RFRkFVTFRfS0VZX1RZUEV9IC1mICR7cXVvdGUoa2V5UGF0aCl9IC1OIFwiXCIgLUMgc2lnbmZpbGVzYCk7XG4gICAgcmV0dXJuIGtleVBhdGg7XG59XG5cbi8qKiBUaGUgcHVibGljIGtleSBpbiB0aGUgZm9ybSB0aGUgZGFlbW9uIHJlcG9ydHMgaXQsIHNvIHdoYXQgaXMgcHJpbnRlZCBoZXJlIGNhbiBiZSBjb21wYXJlZFxuICAgIGFnYWluc3Qgd2hhdCBhcnJpdmVzIG9uIERpc2NvcmQuICovXG5hc3luYyBmdW5jdGlvbiBwdWJsaWNLZXlPZihrZXlQYXRoOiBzdHJpbmcpIHtcbiAgICBsZXQgY29udGVudHMgPSBhd2FpdCBmcy5yZWFkRmlsZShgJHtrZXlQYXRofS5wdWJgLCBcInV0ZjhcIik7XG4gICAgbGV0IFtrZXlUeXBlLCBrZXlCb2R5XSA9IGNvbnRlbnRzLnRyaW0oKS5zcGxpdCgvXFxzKy8pO1xuICAgIGlmICgha2V5VHlwZSB8fCAha2V5Qm9keSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIGEgcHVibGljIGtleSBpbiAke2tleVBhdGh9LnB1Yiwgd2FzICR7Y29udGVudHMuc2xpY2UoMCwgTUFYX0VSUk9SX0JPRFlfTEVOR1RIKX1gKTtcbiAgICB9XG4gICAgcmV0dXJuIGAke2tleVR5cGV9ICR7a2V5Qm9keX1gO1xufVxuXG4vKiogU2lnbmluZyBhIGtleXMgcmVwbyB0aGF0IHN0aWxsIGhvbGRzIGEgcmV2b2tlZCBrZXkgd291bGQgcHVibGlzaCBpdCBiYWNrIHRvIGV2ZXJ5IG1hY2hpbmUgdGhhdFxuICAgIHRvb2sgaXQgb3V0LiBPbmx5IGFwcGxpZXMgdG8gYSByZXBvIHRoYXQgaG9sZHMga2V5cyBhbmQgaGFzIGEgcmV2b2tlIHJlcG8gdG8gY2hlY2sgLSBzaWduaW5nXG4gICAgYW55dGhpbmcgZWxzZSBpcyBub25lIG9mIHRoaXMgZnVuY3Rpb24ncyBidXNpbmVzcy4gKi9cbmFzeW5jIGZ1bmN0aW9uIHJlZnVzZVJldm9rZWRLZXlzKHJlcG9QYXRoOiBzdHJpbmcpIHtcbiAgICBpZiAoIWF3YWl0IHBhdGhFeGlzdHMocGF0aC5qb2luKHJlcG9QYXRoLCBcImF1dGhvcml6ZWRfa2V5c1wiKSkpIHtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBsZXQgb3JpZ2luVVJMID0gKGF3YWl0IHJ1blByb21pc2UoXCJnaXQgcmVtb3RlIGdldC11cmwgb3JpZ2luXCIsIHsgY3dkOiByZXBvUGF0aCwgcXVpZXQ6IHRydWUsIG5vdGhyb3c6IHRydWUgfSkpLnRyaW0oKTtcbiAgICBpZiAoIW9yaWdpblVSTCkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGxldCByZXZva2VkO1xuICAgIHRyeSB7XG4gICAgICAgIHJldm9rZWQgPSBhd2FpdCByZXZva2VkS2V5c0luUmVwbyh7IHJlcG9QYXRoLCBzb3VyY2VVUkw6IG9yaWdpblVSTCB9KTtcbiAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgIC8vIE5vIHJldm9rZSByZXBvLCBvciBubyBhY2Nlc3MgdG8gaXQuIE5vdGhpbmcgaGFzIGJlZW4gcmV2b2tlZCB0aGF0IHdlIGNhbiBzZWUuXG4gICAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKCFyZXZva2VkLmxlbmd0aCkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgYEV4cGVjdGVkICR7cmVwb1BhdGh9IHRvIGhvbGQgbm8gcmV2b2tlZCBrZXlzLCBpdCBob2xkcyAke3Jldm9rZWQubGVuZ3RofTpcXG5gXG4gICAgICAgICsgcmV2b2tlZC5tYXAoZW50cnkgPT4gYCAgJHtlbnRyeS5yZXZvY2F0aW9uLmZpbmdlcnByaW50fSByZXZva2VkIGJ5ICR7ZW50cnkucmV2b2NhdGlvbi5yZXZva2VkQnkgfHwgXCI/XCJ9YFxuICAgICAgICAgICAgKyBgIGFmdGVyIHVzZSBmcm9tICR7ZW50cnkucmV2b2NhdGlvbi5hdHRlbXB0Py5pcCB8fCBcIj9cIn1gKS5qb2luKFwiXFxuXCIpXG4gICAgICAgICsgYFxcbkRlbGV0ZSB0aGVtIGZyb20gYXV0aG9yaXplZF9rZXlzLCBvciBydW4gXCJ5YXJuIHVucmV2b2tlXCIgaGVyZSB0byBhbGxvdyB0aGVtIGFnYWluLmBcbiAgICApO1xufVxuXG5mdW5jdGlvbiBwYXJzZUFyZ3MoYXJndjogc3RyaW5nW10pIHtcbiAgICBsZXQgcHVzaFRvR2l0ID0gYXJndi5pbmNsdWRlcyhHSVRfS0VZV09SRCk7XG4gICAgbGV0IHBvc2l0aW9uYWwgPSBhcmd2LmZpbHRlcihhcmcgPT4gYXJnICE9PSBHSVRfS0VZV09SRCk7XG4gICAgaWYgKHBvc2l0aW9uYWwubGVuZ3RoID4gMSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEV4cGVjdGVkIGF0IG1vc3QgYSBzaWduaW5nIGtleSwgd2FzICR7cG9zaXRpb25hbC5sZW5ndGh9IGFyZ3VtZW50KHMpOiAke3Bvc2l0aW9uYWwuam9pbihcIiBcIil9XFxuJHtVU0FHRX1gKTtcbiAgICB9XG4gICAgcmV0dXJuIHsga2V5UGF0aDogcG9zaXRpb25hbFswXSwgcHVzaFRvR2l0IH07XG59XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBtYWluKCkge1xuICAgIGxldCB7IGtleVBhdGgsIHB1c2hUb0dpdCB9ID0gcGFyc2VBcmdzKHByb2Nlc3MuYXJndi5zbGljZSgyKSk7XG5cbiAgICBsZXQgcmVwb1BhdGggPSAoYXdhaXQgcnVuUHJvbWlzZShcImdpdCByZXYtcGFyc2UgLS1zaG93LXRvcGxldmVsXCIsIHsgcXVpZXQ6IHRydWUgfSkpLnRyaW0oKTtcbiAgICBpZiAoIXJlcG9QYXRoKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgRXhwZWN0ZWQgdGhlIGN1cnJlbnQgZGlyZWN0b3J5IHRvIGJlIGluc2lkZSBhIGdpdCByZXBvLCBpdCBpcyBub3QuXFxuJHtVU0FHRX1gKTtcbiAgICB9XG5cbiAgICBsZXQgc2lnbmluZ0tleSA9IGtleVBhdGggJiYgZXhwYW5kSG9tZShrZXlQYXRoKSB8fCBhd2FpdCBlbnN1cmVEZWZhdWx0S2V5KCk7XG4gICAgaWYgKCFhd2FpdCBwYXRoRXhpc3RzKHNpZ25pbmdLZXkpKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgRXhwZWN0ZWQgYSBzaWduaW5nIGtleSBhdCAke3NpZ25pbmdLZXl9LCBubyBzdWNoIGZpbGUgZXhpc3RzYCk7XG4gICAgfVxuXG4gICAgYXdhaXQgcmVmdXNlUmV2b2tlZEtleXMocmVwb1BhdGgpO1xuXG4gICAgbGV0IG1hbmlmZXN0ID0gYXdhaXQgYnVpbGRNYW5pZmVzdChyZXBvUGF0aCk7XG4gICAgY29uc29sZS5sb2coYCR7TUFOSUZFU1RfTkFNRX0gY292ZXJzICR7bWFuaWZlc3QuZmlsZXMubGVuZ3RofSBmaWxlKHMpIGluICR7cmVwb1BhdGh9YCk7XG5cbiAgICAvLyBUaGUgbWFuaWZlc3QgaXMgYnVpbHQgYW5kIHNpZ25lZCBhd2F5IGZyb20gdGhlIHJlcG8sIGFuZCBvbmx5IG1vdmVkIGluIG9uY2UgYm90aCBleGlzdC5cbiAgICAvLyBMYW5kaW5nIGEgbmV3IG1hbmlmZXN0IG5leHQgdG8gYW4gb2xkIHNpZ25hdHVyZSBwcm9kdWNlcyBhIHBhaXIgdGhhdCBjYW4gbmV2ZXIgdmVyaWZ5LCBhbmRcbiAgICAvLyB0aGUgZGFlbW9uIHJlYWRpbmcgaXQgY2FuIG9ubHkgdHJlYXQgdGhhdCBhcyB0YW1wZXJpbmcuXG4gICAgbGV0IHN0YWdpbmdEaXJlY3RvcnkgPSBhd2FpdCBmcy5ta2R0ZW1wKHBhdGguam9pbihvcy50bXBkaXIoKSwgXCJzaWduZmlsZXMtXCIpKTtcbiAgICBsZXQgc3RhZ2VkTWFuaWZlc3QgPSBwYXRoLmpvaW4oc3RhZ2luZ0RpcmVjdG9yeSwgTUFOSUZFU1RfTkFNRSk7XG4gICAgbGV0IHN0YWdlZFNpZ25hdHVyZSA9IGAke3N0YWdlZE1hbmlmZXN0fS5zaWdgO1xuICAgIGF3YWl0IGZzLndyaXRlRmlsZShzdGFnZWRNYW5pZmVzdCwgZm9ybWF0TWFuaWZlc3QobWFuaWZlc3QpKTtcblxuICAgIC8vIFNpZ25pbmcgaGFwcGVucyBiZWZvcmUgYW55IGdpdCB3b3JrLCBzbyBhIGZhaWxlZCBwdXNoIG5ldmVyIGNvc3RzIGEgc2Vjb25kIHRvdWNoIG9mIHRoZSBrZXkuXG4gICAgYXdhaXQgcnVuUHJvbWlzZShgc3NoLWtleWdlbiAtWSBzaWduIC1mICR7cXVvdGUoc2lnbmluZ0tleSl9IC1uICR7U0lHTl9OQU1FU1BBQ0V9ICR7cXVvdGUoc3RhZ2VkTWFuaWZlc3QpfWApO1xuICAgIC8vIENoZWNrZWQgaGVyZSByYXRoZXIgdGhhbiBsZWZ0IGZvciBhIG1hY2hpbmUgdGhhdCBoYXMgYWxyZWFkeSBwdWxsZWQgaXQgdG8gZGlzY292ZXIuXG4gICAgYXdhaXQgcnVuUHJvbWlzZShcbiAgICAgICAgYHNzaC1rZXlnZW4gLVkgY2hlY2stbm92YWxpZGF0ZSAtbiAke1NJR05fTkFNRVNQQUNFfSAtcyAke3F1b3RlKHN0YWdlZFNpZ25hdHVyZSl9IDwgJHtxdW90ZShzdGFnZWRNYW5pZmVzdCl9YFxuICAgICk7XG4gICAgYXdhaXQgZnMuY29weUZpbGUoc3RhZ2VkTWFuaWZlc3QsIHBhdGguam9pbihyZXBvUGF0aCwgTUFOSUZFU1RfTkFNRSkpO1xuICAgIGF3YWl0IGZzLmNvcHlGaWxlKHN0YWdlZFNpZ25hdHVyZSwgcGF0aC5qb2luKHJlcG9QYXRoLCBTSUdOQVRVUkVfTkFNRSkpO1xuICAgIGF3YWl0IGZzLnJtKHN0YWdpbmdEaXJlY3RvcnksIHsgcmVjdXJzaXZlOiB0cnVlLCBmb3JjZTogdHJ1ZSB9KTtcbiAgICBjb25zb2xlLmxvZyhgU2lnbmVkIHdpdGggJHthd2FpdCBwdWJsaWNLZXlPZihzaWduaW5nS2V5KX1gKTtcblxuICAgIGlmICghcHVzaFRvR2l0KSB7XG4gICAgICAgIGNvbnNvbGUubG9nKGBDb21taXQgYW5kIHB1c2ggJHtNQU5JRkVTVF9OQU1FfSBhbmQgJHtTSUdOQVRVUkVfTkFNRX0gZm9yIGFueXRoaW5nIHRvIHNlZSB0aGVtLmApO1xuICAgICAgICByZXR1cm47XG4gICAgfVxuICAgIGF3YWl0IHJ1blByb21pc2UoYGdpdCBhZGQgLUFgLCB7IGN3ZDogcmVwb1BhdGggfSk7XG4gICAgLy8gTm90aGluZyBzdGFnZWQgaXMgbm90IHdvcnRoIHN0b3BwaW5nIG9uLiBnaXQgY29tbWl0IGNhbGxzIHRoYXQgYSBmYWlsdXJlLCBidXQgaXQgb25seSBtZWFuc1xuICAgIC8vIHRoZSBzaWduYXR1cmUgbWF0Y2hlcyB0aGUgb25lIGFscmVhZHkgY29tbWl0dGVkLCBzbyB0aGVyZSBpcyBub3RoaW5nIHRvIGRlcGxveS5cbiAgICBsZXQgc3RhZ2VkID0gYXdhaXQgcnVuUHJvbWlzZShgZ2l0IHN0YXR1cyAtLXBvcmNlbGFpbmAsIHsgY3dkOiByZXBvUGF0aCwgcXVpZXQ6IHRydWUgfSk7XG4gICAgaWYgKCFzdGFnZWQudHJpbSgpKSB7XG4gICAgICAgIGNvbnNvbGUubG9nKGBOb3RoaW5nIGNoYW5nZWQsICR7TUFOSUZFU1RfTkFNRX0gYW5kICR7U0lHTkFUVVJFX05BTUV9IGFyZSBhbHJlYWR5IGNvbW1pdHRlZC5gKTtcbiAgICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICBhd2FpdCBydW5Qcm9taXNlKGBnaXQgY29tbWl0IC1tICR7cXVvdGUoQ09NTUlUX01FU1NBR0UpfWAsIHsgY3dkOiByZXBvUGF0aCB9KTtcbiAgICBhd2FpdCBydW5Qcm9taXNlKGBnaXQgcHVzaGAsIHsgY3dkOiByZXBvUGF0aCB9KTtcbiAgICBjb25zb2xlLmxvZyhgQ29tbWl0dGVkIGFuZCBwdXNoZWQuYCk7XG59XG4iXX0=
|
|
146
|
+
/* _JS_SOURCE_HASH = "534e0243c3e52f007b386e8947198cdd98869d9cc5f9dda1413aeecb36b947a7"; */
|