244 lines
5.6 KiB
JavaScript
Executable File
244 lines
5.6 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
const inquirer = require("inquirer");
|
|
const { SSM } = require("@aws-sdk/client-ssm");
|
|
const { initSignalHandlers } = require("./util");
|
|
const fetch = require("node-fetch");
|
|
|
|
let ssm;
|
|
|
|
const Commands = [
|
|
"code",
|
|
"ssh",
|
|
"web",
|
|
"disconnect"
|
|
];
|
|
|
|
initSignalHandlers();
|
|
main();
|
|
|
|
async function main() {
|
|
await getAWSCredentials();
|
|
|
|
const { command } = await inquirer.prompt({
|
|
type: "list",
|
|
name: "command",
|
|
choices: Commands
|
|
});
|
|
|
|
switch (command) {
|
|
case "code":
|
|
return await commandCode();
|
|
|
|
case "ssh":
|
|
return await commandSsh();
|
|
|
|
case "web":
|
|
return await commandWeb();
|
|
|
|
case "disconnect":
|
|
return await commandDisconnect();
|
|
}
|
|
}
|
|
|
|
async function commandCode() {
|
|
await closeTunnels();
|
|
await updateNgrokAuthToken();
|
|
|
|
const code = `000000${Math.random() * 999999}`.slice(-6);
|
|
await saveParam("code", code);
|
|
|
|
console.log(`The code is: ${code}`);
|
|
}
|
|
|
|
async function commandSsh() {
|
|
const tunnels = await loadTunnels();
|
|
|
|
if (!tunnels.length) {
|
|
console.log("There are no tunnels!");
|
|
return;
|
|
}
|
|
|
|
const sshTunnel = tunnels.find(tunnel => tunnel.proto === "tcp");
|
|
const [ , host, port ] = sshTunnel.public_url.match(/tcp:\/\/([^:]+):(\d+)/);
|
|
|
|
console.log("Run this:");
|
|
console.log();
|
|
console.log(`ssh bbmc@${host} -p ${port}`);
|
|
console.log();
|
|
}
|
|
|
|
async function commandWeb() {
|
|
const url = await getWebUrl();
|
|
|
|
console.log(`Web interface: ${url}`);
|
|
console.log();
|
|
}
|
|
|
|
async function commandDisconnect() {
|
|
const tunnels = await loadTunnels();
|
|
|
|
if (!tunnels.length) {
|
|
console.log("There are no tunnels!");
|
|
process.exit(1);
|
|
}
|
|
|
|
const webTunnel = tunnels.find(tunnel => tunnel.proto === "https");
|
|
|
|
const response = await fetch(`${webTunnel.public_url}/api/remote-diagnostics?command=disconnect`, {
|
|
headers: {
|
|
Authorization: `Basic ${Buffer.from("onefinity:onefinity").toString("base64")}`
|
|
}
|
|
});
|
|
|
|
console.log();
|
|
console.log(await response.text());
|
|
console.log();
|
|
console.log("We now need a new ngrok auth token.");
|
|
|
|
await promptForNewAuthToken();
|
|
}
|
|
|
|
async function updateNgrokAuthToken() {
|
|
const storedAuthToken = await loadParam("ngrok-auth-token", "");
|
|
|
|
const apiKey = await loadParam("ngrok-api-key");
|
|
const response = await fetch("https://api.ngrok.com/credentials", {
|
|
headers: {
|
|
"Authorization": `Bearer ${apiKey}`,
|
|
"Ngrok-Version": "2"
|
|
}
|
|
});
|
|
|
|
const result = await response.json();
|
|
let authToken = result.credentials[0].token;
|
|
|
|
if (authToken === storedAuthToken) {
|
|
console.warn("The nGrok AuthToken is stale.");
|
|
|
|
const { ignore } = await inquirer.prompt({
|
|
type: "confirm",
|
|
name: "ignore"
|
|
});
|
|
|
|
if (!ignore) {
|
|
authToken = await promptForNewAuthToken();
|
|
}
|
|
}
|
|
|
|
await saveParam("ngrok-auth-token", authToken);
|
|
}
|
|
|
|
async function promptForNewAuthToken() {
|
|
console.warn("To re-issue, visit: https://dashboard.ngrok.com/get-started/your-authtoken");
|
|
const { newAuthToken } = await inquirer.prompt({
|
|
type: "input",
|
|
name: "newAuthToken"
|
|
});
|
|
|
|
return newAuthToken;
|
|
}
|
|
|
|
function getParamName(name) {
|
|
return `/onefinity-support/${name}`;
|
|
}
|
|
|
|
async function loadParam(name, defaultValue = undefined) {
|
|
name = getParamName(name);
|
|
|
|
try {
|
|
const response = await ssm.getParameter({ Name: name });
|
|
return response.Parameter.Value;
|
|
} catch (error) {
|
|
if (error.name !== "ParameterNotFound") {
|
|
console.log(`Error getting parameter "${name}"`, JSON.stringify({
|
|
name: error.name,
|
|
message: error.message,
|
|
stack: error.stack,
|
|
cause: error.cause
|
|
}, null, 4));
|
|
}
|
|
|
|
if (defaultValue === undefined) {
|
|
throw error;
|
|
}
|
|
|
|
return defaultValue;
|
|
}
|
|
}
|
|
|
|
async function saveParam(name, value) {
|
|
await ssm.putParameter({
|
|
Name: getParamName(name),
|
|
Value: value,
|
|
DataType: "text",
|
|
Overwrite: true,
|
|
Tier: "Standard",
|
|
Type: "String"
|
|
});
|
|
}
|
|
|
|
async function getAWSCredentials() {
|
|
const { accessKeyId, secretAccessKey } = await inquirer.prompt([
|
|
{
|
|
type: "input",
|
|
name: "accessKeyId"
|
|
},
|
|
{
|
|
type: "input",
|
|
name: "secretAccessKey"
|
|
}
|
|
]);
|
|
|
|
ssm = new SSM({
|
|
credentials: {
|
|
accessKeyId,
|
|
secretAccessKey
|
|
},
|
|
region: "us-east-1"
|
|
});
|
|
}
|
|
|
|
async function loadTunnels() {
|
|
const apiKey = await loadParam("ngrok-api-key");
|
|
const response = await fetch("https://api.ngrok.com/tunnels", {
|
|
headers: {
|
|
Authorization: `Bearer ${apiKey}`,
|
|
"Ngrok-Version": 2
|
|
}
|
|
});
|
|
|
|
const { tunnels } = await response.json();
|
|
|
|
return tunnels;
|
|
}
|
|
|
|
async function getWebUrl() {
|
|
const tunnels = await loadTunnels();
|
|
|
|
if (!tunnels.length) {
|
|
console.log("There are no tunnels!");
|
|
process.exit(1);
|
|
}
|
|
|
|
const webTunnel = tunnels.find(tunnel => tunnel.proto === "https");
|
|
const url = new URL(webTunnel.public_url);
|
|
url.username = "onefinity";
|
|
url.password = "onefinity";
|
|
url.protocol = "http";
|
|
|
|
return url.toString();
|
|
}
|
|
|
|
async function closeTunnels() {
|
|
const tunnels = await loadTunnels();
|
|
|
|
if (!tunnels?.length) {
|
|
return;
|
|
}
|
|
|
|
console.error("There are tunnels open:", JSON.stringify(tunnels, null, 4));
|
|
console.error("Giving up");
|
|
process.exit(1);
|
|
}
|