Effective teardown, and Sentry integration
This commit is contained in:
parent
46bbfa8844
commit
f19e850e0d
|
@ -9,7 +9,7 @@ import rpc from "vscode-jsonrpc";
|
|||
|
||||
import { langs } from "./langs.js";
|
||||
import * as util from "./util.js";
|
||||
import { bash, getUUID } from "./util.js";
|
||||
import { bash, getUUID, logError } from "./util.js";
|
||||
|
||||
const allSessions = new Set();
|
||||
|
||||
|
@ -180,12 +180,11 @@ export class Session {
|
|||
await this.teardown();
|
||||
});
|
||||
this.ws.on("error", async (err) => {
|
||||
this.log(`Websocket error: ${err}`);
|
||||
logError(err);
|
||||
await this.teardown();
|
||||
});
|
||||
} catch (err) {
|
||||
this.log(`Error while setting up environment`);
|
||||
console.log(err);
|
||||
logError(err);
|
||||
this.sendError(err);
|
||||
await this.teardown();
|
||||
}
|
||||
|
@ -198,8 +197,7 @@ export class Session {
|
|||
}
|
||||
this.ws.send(JSON.stringify(msg));
|
||||
} catch (err) {
|
||||
this.log(`Failed to send websocket message: ${err}`);
|
||||
console.log(err);
|
||||
logError(err);
|
||||
await this.teardown();
|
||||
}
|
||||
};
|
||||
|
@ -280,8 +278,7 @@ export class Session {
|
|||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
this.log(`Error while handling message from client`);
|
||||
console.log(err);
|
||||
logError(err);
|
||||
this.sendError(err);
|
||||
}
|
||||
};
|
||||
|
@ -361,8 +358,7 @@ export class Session {
|
|||
}
|
||||
});
|
||||
} catch (err) {
|
||||
this.log(`Error while running user code`);
|
||||
console.log(err);
|
||||
logError(err);
|
||||
this.sendError(err);
|
||||
}
|
||||
};
|
||||
|
@ -429,8 +425,7 @@ export class Session {
|
|||
});
|
||||
this.formatter = formatter;
|
||||
} catch (err) {
|
||||
this.log(`Error while running code formatter`);
|
||||
console.log(err);
|
||||
logError(err);
|
||||
this.sendError(err);
|
||||
}
|
||||
};
|
||||
|
@ -454,11 +449,11 @@ export class Session {
|
|||
if (this.container) {
|
||||
this.container.proc.kill();
|
||||
}
|
||||
await this.run(this.privilegedTeardown({ uuid }));
|
||||
allSessions.delete(this);
|
||||
this.ws.terminate();
|
||||
} catch (err) {
|
||||
this.log(`Error during teardown`);
|
||||
console.log(err);
|
||||
logError(err);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -8,7 +8,8 @@ import _ from "lodash";
|
|||
|
||||
import * as api from "./api.js";
|
||||
import { aliases, langs } from "./langs.js";
|
||||
import { log } from "./util.js";
|
||||
import * as util from "./util.js";
|
||||
import { log, privilegedTeardown } from "./util.js";
|
||||
|
||||
const host = process.env.HOST || "localhost";
|
||||
const port = parseInt(process.env.PORT || "") || 6119;
|
||||
|
@ -96,6 +97,10 @@ function addWebsocket(baseApp, httpsServer) {
|
|||
return app;
|
||||
}
|
||||
|
||||
util.run(privilegedTeardown(), console.error).catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
if (useTLS) {
|
||||
const httpsServer = https.createServer(
|
||||
{
|
||||
|
|
|
@ -4,6 +4,22 @@ import process from "process";
|
|||
|
||||
import { v4 as getUUIDOrig } from "uuid";
|
||||
|
||||
let sentryEnabled = false;
|
||||
|
||||
if (process.env.SENTRY_DSN) {
|
||||
Sentry.init({
|
||||
dsn: process.env.SENTRY_DSN,
|
||||
});
|
||||
sentryEnabled = true;
|
||||
}
|
||||
|
||||
export function logError(err) {
|
||||
console.error(err);
|
||||
if (sentryEnabled) {
|
||||
Sentry.captureException(err);
|
||||
}
|
||||
}
|
||||
|
||||
function computeImageHashes() {
|
||||
let deployConfig = process.env.RIJU_DEPLOY_CONFIG;
|
||||
if (!deployConfig) return {};
|
||||
|
@ -85,6 +101,16 @@ export function privilegedPty({ uuid }, args) {
|
|||
return [rijuSystemPrivileged, "pty", uuid].concat(args);
|
||||
}
|
||||
|
||||
export function privilegedTeardown(options) {
|
||||
options = options || {};
|
||||
const { uuid } = options;
|
||||
const cmdline = [rijuSystemPrivileged, "teardown"];
|
||||
if (uuid) {
|
||||
cmdline.push(uuid);
|
||||
}
|
||||
return cmdline;
|
||||
}
|
||||
|
||||
export function bash(cmdline, opts) {
|
||||
const stty = opts && opts.stty;
|
||||
if (!cmdline.match(/[;|&(){}=\n]/)) {
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
"@babel/parser": "^7.13.11",
|
||||
"@babel/preset-env": "^7.12.11",
|
||||
"@balena/dockerignore": "^1.0.2",
|
||||
"@sentry/node": "^6.11.0",
|
||||
"async-lock": "^1.2.6",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-walk": "^3.0.0",
|
||||
|
|
|
@ -61,6 +61,7 @@ sudo sed -Ei 's/^#?PermitEmptyPasswords .*/PermitEmptyPasswords no/' /etc/ssh/ss
|
|||
sudo sed -Ei "s/\\\$AWS_REGION/${AWS_REGION}/" /etc/systemd/system/riju.service
|
||||
sudo sed -Ei "s/\\\$FATHOM_SITE_ID/${FATHOM_SITE_ID:-}/" /etc/systemd/system/riju.service
|
||||
sudo sed -Ei "s/\\\$S3_BUCKET/${S3_BUCKET}/" /etc/systemd/system/riju.service
|
||||
sudo sed -Ei "s/\\\$SENTRY_DSN/${SENTRY_DSN:-}/" /etc/systemd/system/riju.service
|
||||
sudo sed -Ei "s/\\\$SUPERVISOR_ACCESS_TOKEN/${SUPERVISOR_ACCESS_TOKEN}/" /etc/systemd/system/riju.service
|
||||
|
||||
sudo passwd -l root
|
||||
|
|
|
@ -13,6 +13,7 @@ RestartSec=5
|
|||
Environment=AWS_REGION=$AWS_REGION
|
||||
Environment=FATHOM_SITE_ID=$FATHOM_SITE_ID
|
||||
Environment=S3_BUCKET=$S3_BUCKET
|
||||
Environment=SENTRY_DSN=$SENTRY_DSN
|
||||
Environment=SUPERVISOR_ACCESS_TOKEN=$SUPERVISOR_ACCESS_TOKEN
|
||||
|
||||
[Install]
|
||||
|
|
|
@ -23,6 +23,11 @@ variable "s3_bucket" {
|
|||
default = "${env("S3_BUCKET")}"
|
||||
}
|
||||
|
||||
variable "sentry_dsn" {
|
||||
type = string
|
||||
default = "${env("SENTRY_DSN_PACKER")}"
|
||||
}
|
||||
|
||||
variable "supervisor_access_token" {
|
||||
type = string
|
||||
default = "${env("SUPERVISOR_ACCESS_TOKEN")}"
|
||||
|
@ -114,6 +119,7 @@ build {
|
|||
"FATHOM_SITE_ID=${var.fathom_site_id}",
|
||||
"GRAFANA_API_KEY=${var.grafana_api_key}",
|
||||
"S3_BUCKET=${var.s3_bucket}",
|
||||
"SENTRY_DSN=${var.sentry_dsn}",
|
||||
"SUPERVISOR_ACCESS_TOKEN=${var.supervisor_access_token}",
|
||||
]
|
||||
script = "provision-web.bash"
|
||||
|
|
|
@ -349,6 +349,7 @@ func (sv *supervisor) reload() error {
|
|||
"-p", fmt.Sprintf("127.0.0.1:%d:6119", port),
|
||||
"-e", "FATHOM_SITE_ID",
|
||||
"-e", "RIJU_DEPLOY_CONFIG",
|
||||
"-e", "SENTRY_DSN",
|
||||
"--label", fmt.Sprintf("riju.deploy-config-hash=%s", deployCfgHash),
|
||||
"--name", name,
|
||||
"--restart", "unless-stopped",
|
||||
|
|
|
@ -32,7 +32,8 @@ void die_with_usage()
|
|||
die("usage:\n"
|
||||
" riju-system-privileged session UUID LANG [IMAGE-HASH]\n"
|
||||
" riju-system-privileged exec UUID CMDLINE...\n"
|
||||
" riju-system-privileged pty UUID CMDLINE...");
|
||||
" riju-system-privileged pty UUID CMDLINE...\n"
|
||||
" riju-system-privileged teardown [UUID]");
|
||||
}
|
||||
|
||||
char *quoteArgs(int argc, char **cmdline)
|
||||
|
@ -128,7 +129,10 @@ void session(char *uuid, char *lang, char *imageHash)
|
|||
{
|
||||
if (setvbuf(stdout, NULL, _IONBF, 0) != 0)
|
||||
die("setvbuf failed");
|
||||
char *image, *container, *hostname, *share, *volume, *fifo, *rijuPtyPath;
|
||||
char *image, *container, *hostname, *share, *volume, *fifo, *rijuPtyPath,
|
||||
*sessionLabel;
|
||||
if (asprintf(&sessionLabel, "riju.user-session=%s", uuid) < 0)
|
||||
die("asprintf failed");
|
||||
if ((imageHash != NULL ? asprintf(&image, "riju:lang-%s-%s", lang, imageHash)
|
||||
: asprintf(&image, "riju:lang-%s", lang)) < 0)
|
||||
die("asprintf failed");
|
||||
|
@ -231,6 +235,10 @@ void session(char *uuid, char *lang, char *imageHash)
|
|||
"2048",
|
||||
"--cgroup-parent",
|
||||
"riju.slice",
|
||||
"--label",
|
||||
"riju.category=user-session",
|
||||
"--label",
|
||||
sessionLabel,
|
||||
image,
|
||||
"bash",
|
||||
"-c",
|
||||
|
@ -403,11 +411,28 @@ void exec(char *uuid, int argc, char **cmdline, bool pty)
|
|||
}
|
||||
}
|
||||
|
||||
void teardown(char *uuid)
|
||||
{
|
||||
char *cmdline;
|
||||
if (uuid != NULL) {
|
||||
if (asprintf(&cmdline, "rm -rf /var/cache/riju/shares/%s", uuid) < 0)
|
||||
die("asprintf failed");
|
||||
} else {
|
||||
cmdline = "comm -23 <(sudo ls /var/cache/riju/shares | sort) <(docker ps "
|
||||
"-f label=riju.category=user-session --format \"{{ .Labels }}\" "
|
||||
"| grep -Eo 'riju\\.user-session=[a-z0-9]+' | sed -E "
|
||||
"'s/^[^=]+=//') | (cd /var/cache/riju/shares; xargs rm -rf)";
|
||||
}
|
||||
char *argv[] = {"bash", "-c", cmdline, NULL};
|
||||
execvp(argv[0], argv);
|
||||
die("execvp failed");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
init();
|
||||
if (seteuid(0) != 0)
|
||||
die("seteuid failed");
|
||||
if (setuid(0) != 0)
|
||||
die("setuid failed");
|
||||
if (argc < 2)
|
||||
die_with_usage();
|
||||
if (!strcmp(argv[1], "session")) {
|
||||
|
@ -431,5 +456,11 @@ int main(int argc, char **argv)
|
|||
exec(parseUUID(argv[2]), argc - 3, &argv[3], true);
|
||||
return 0;
|
||||
}
|
||||
if (!strcmp(argv[1], "teardown")) {
|
||||
if (argc < 2)
|
||||
die_with_usage();
|
||||
teardown(argc >= 3 ? parseUUID(argv[2]) : NULL);
|
||||
return 0;
|
||||
}
|
||||
die_with_usage();
|
||||
}
|
||||
|
|
105
yarn.lock
105
yarn.lock
|
@ -878,6 +878,74 @@
|
|||
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz#90420f9f9c6d3987f176a19a7d8e764271a2f55d"
|
||||
integrity sha512-Fxt+AfXgjMoin2maPIYzFZnQjAXjAL0PHscM5pRTtatFqB+vZxAM9tLp2Optnuw3QOQC40jTNeGYFOMvyf7v9g==
|
||||
|
||||
"@sentry/core@6.11.0":
|
||||
version "6.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.11.0.tgz#40e94043afcf6407a109be26655c77832c64e740"
|
||||
integrity sha512-09TB+f3pqEq8LFahFWHO6I/4DxHo+NcS52OkbWMDqEi6oNZRD7PhPn3i14LfjsYVv3u3AESU8oxSEGbFrr2UjQ==
|
||||
dependencies:
|
||||
"@sentry/hub" "6.11.0"
|
||||
"@sentry/minimal" "6.11.0"
|
||||
"@sentry/types" "6.11.0"
|
||||
"@sentry/utils" "6.11.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/hub@6.11.0":
|
||||
version "6.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.11.0.tgz#ddf9ddb0577d1c8290dc02c0242d274fe84d6c16"
|
||||
integrity sha512-pT9hf+ZJfVFpoZopoC+yJmFNclr4NPqPcl2cgguqCHb69DklD1NxgBNWK8D6X05qjnNFDF991U6t1mxP9HrGuw==
|
||||
dependencies:
|
||||
"@sentry/types" "6.11.0"
|
||||
"@sentry/utils" "6.11.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/minimal@6.11.0":
|
||||
version "6.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.11.0.tgz#806d5512658370e40827b3e3663061db708fff33"
|
||||
integrity sha512-XkZ7qrdlGp4IM/gjGxf1Q575yIbl5RvPbg+WFeekpo16Ufvzx37Mr8c2xsZaWosISVyE6eyFpooORjUlzy8EDw==
|
||||
dependencies:
|
||||
"@sentry/hub" "6.11.0"
|
||||
"@sentry/types" "6.11.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/node@^6.11.0":
|
||||
version "6.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-6.11.0.tgz#62614c18af779373a12311f2fb57a8dde278425a"
|
||||
integrity sha512-vbk+V/n7ZIFD8rHPYy03t/gIG5V7LGdjU4qJxVDgNZzticfWPnd2sLgle/r+l60XF6SKW/epG4rnxnBcgPdWaw==
|
||||
dependencies:
|
||||
"@sentry/core" "6.11.0"
|
||||
"@sentry/hub" "6.11.0"
|
||||
"@sentry/tracing" "6.11.0"
|
||||
"@sentry/types" "6.11.0"
|
||||
"@sentry/utils" "6.11.0"
|
||||
cookie "^0.4.1"
|
||||
https-proxy-agent "^5.0.0"
|
||||
lru_map "^0.3.3"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/tracing@6.11.0":
|
||||
version "6.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-6.11.0.tgz#9bd9287addea1ebc12c75b226f71c7713c0fac4f"
|
||||
integrity sha512-9VA1/SY++WeoMQI4K6n/sYgIdRtCu9NLWqmGqu/5kbOtESYFgAt1DqSyqGCr00ZjQiC2s7tkDkTNZb38K6KytQ==
|
||||
dependencies:
|
||||
"@sentry/hub" "6.11.0"
|
||||
"@sentry/minimal" "6.11.0"
|
||||
"@sentry/types" "6.11.0"
|
||||
"@sentry/utils" "6.11.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sentry/types@6.11.0":
|
||||
version "6.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.11.0.tgz#5122685478d32ddacd3a891cbcf550012df85f7c"
|
||||
integrity sha512-gm5H9eZhL6bsIy/h3T+/Fzzz2vINhHhqd92CjHle3w7uXdTdFV98i2pDpErBGNTSNzbntqOMifYEB5ENtZAvcg==
|
||||
|
||||
"@sentry/utils@6.11.0":
|
||||
version "6.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.11.0.tgz#d1dee4faf4d9c42c54bba88d5a66fb96b902a14c"
|
||||
integrity sha512-IOvyFHcnbRQxa++jO+ZUzRvFHEJ1cZjrBIQaNVc0IYF0twUOB5PTP6joTcix38ldaLeapaPZ9LGfudbvYvxkdg==
|
||||
dependencies:
|
||||
"@sentry/types" "6.11.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6":
|
||||
version "7.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad"
|
||||
|
@ -1068,6 +1136,13 @@ acorn@^6.4.1:
|
|||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6"
|
||||
integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==
|
||||
|
||||
agent-base@6:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
|
||||
integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
|
||||
dependencies:
|
||||
debug "4"
|
||||
|
||||
ajv-errors@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d"
|
||||
|
@ -1691,6 +1766,11 @@ cookie@0.4.0:
|
|||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
|
||||
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
|
||||
|
||||
cookie@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1"
|
||||
integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==
|
||||
|
||||
copy-concurrently@^1.0.0:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0"
|
||||
|
@ -1816,6 +1896,13 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
|
|||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@4:
|
||||
version "4.3.2"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
|
||||
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
|
||||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
debug@^4.1.0, debug@^4.1.1:
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
|
||||
|
@ -2498,6 +2585,14 @@ https-browserify@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
|
||||
integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
|
||||
|
||||
https-proxy-agent@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
|
||||
integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==
|
||||
dependencies:
|
||||
agent-base "6"
|
||||
debug "4"
|
||||
|
||||
human-signals@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
|
||||
|
@ -2882,6 +2977,11 @@ lru-cache@^6.0.0:
|
|||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
lru_map@^0.3.3:
|
||||
version "0.3.3"
|
||||
resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd"
|
||||
integrity sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0=
|
||||
|
||||
make-dir@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
|
||||
|
@ -4202,6 +4302,11 @@ toidentifier@1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
|
||||
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
|
||||
|
||||
tslib@^1.9.3:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tty-browserify@0.0.0:
|
||||
version "0.0.0"
|
||||
resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
|
||||
|
|
Loading…
Reference in New Issue