From e4037ddaf47fa632d96554d1137ca8c9bf4850ec Mon Sep 17 00:00:00 2001 From: Jonathan Sheely Date: Sat, 25 Sep 2021 12:27:33 -0400 Subject: [PATCH 1/6] Store SSH config --- src/config.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/config.ts b/src/config.ts index e9a104a..48c8d03 100644 --- a/src/config.ts +++ b/src/config.ts @@ -131,3 +131,28 @@ export async function getConfiguration( publicKey, }; } + +async function addHost({ + projectName, + projectUrl, +}: { + projectName: string; + projectUrl: string; +}) { + const homeConfigDir = join(homedir(), '.ssh'); + const configFile = join(homeConfigDir, 'config'); + const config = await readFile(configFile, 'utf8'); + + // Check if host already exists + if (!config.includes(projectUrl)) { + const host = `\nHost ${projectName} + HostName ${projectUrl} + User dappstarter + IdentityFile ${join(homeConfigDir, '.dappstarter', projectName, 'privatekey')} + ForwardAgent yes + ServerAliveInterval 15 + ServerAliveCountMax 4 + `; + await appendFileSync(configFile, host, { mode: 0o600 }); + } +} From ca594e7b41f5d1bae352187d5f9d100632c62ab4 Mon Sep 17 00:00:00 2001 From: Jonathan Sheely Date: Sat, 25 Sep 2021 12:27:43 -0400 Subject: [PATCH 2/6] Store project name --- src/config.ts | 21 +++++++++++++++++---- src/develop.ts | 1 + src/ssh.ts | 8 +++++--- src/types.ts | 1 + 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/config.ts b/src/config.ts index 48c8d03..3c591a9 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,4 +1,10 @@ -import { pathExists, readFile, readJSON, writeJSON } from 'fs-extra'; +import { + pathExists, + readFile, + readJSON, + writeJSON, + appendFileSync, +} from 'fs-extra'; import yaml from 'js-yaml'; import { log } from './utils'; import chalk from 'chalk'; @@ -9,8 +15,8 @@ import hash from 'string-hash'; export const REQUEST_TIMEOUT: number = 10 * 1000; export const CONFIG_FILE = 'config.json'; export let SERVICE_URL = -process.env.DAPPSTARTER_SERVICE_URL || -'https://dappstarter-api.decentology.com'; + process.env.DAPPSTARTER_SERVICE_URL || + 'https://dappstarter-api.decentology.com'; export let PORTS = [5000, 5001, 5002, 8080, 8899, 8900, 12537]; export let CUSTOM_PORTS = false; @@ -117,16 +123,23 @@ export async function storeConfigurationFile( config: DevelopConfigBase ) { await writeJSON(filePath, config, { spaces: 4 }); + await addHost({ + projectName: config.projectName, + projectUrl: config.projectUrl, + }); log(chalk.blueBright('[CONFIG] Configuration file saved: ' + filePath)); } export async function getConfiguration( filePath: string ): Promise { - const { projectUrl } = await readJSON(join(filePath, 'config.json')); + const { projectUrl, projectName } = await readJSON( + join(filePath, 'config.json') + ); const privateKey = await readFile(join(filePath, 'privatekey'), 'utf8'); const publicKey = await readFile(join(filePath, 'publickey'), 'utf8'); return { projectUrl, + projectName, privateKey, publicKey, }; diff --git a/src/develop.ts b/src/develop.ts index a27fd25..d36639e 100755 --- a/src/develop.ts +++ b/src/develop.ts @@ -107,6 +107,7 @@ async function initialize({ await storeConfigurationFile(configFilePath, { projectUrl, + projectName, }); if (!(await isSshOpen(projectUrl))) { diff --git a/src/ssh.ts b/src/ssh.ts index b1f533d..8a056bd 100755 --- a/src/ssh.ts +++ b/src/ssh.ts @@ -1,5 +1,6 @@ import { lookup } from 'dns/promises'; -import { writeFile } from 'fs-extra'; +import { appendFileSync, readFile, writeFile } from 'fs-extra'; +import { homedir } from 'os'; import { join } from 'path'; import keypair from 'keypair'; import forge from 'node-forge'; @@ -187,14 +188,14 @@ export async function forwardPorts( spinner.text = portText; }) ); - + if (!silent) { spinner.stopAndPersist({ symbol: emoji.get('heavy_check_mark'), text: portText, }); } - + process.stdin.resume(); return true; } @@ -340,3 +341,4 @@ export async function createKeys(homeConfigDir: string) { publicKey: publicSSH_key, }; } + diff --git a/src/types.ts b/src/types.ts index a5926dc..ff4fb16 100755 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,6 @@ export type DevelopConfigBase = { projectUrl: string; + projectName: string; }; export type DevelopConfig = { From 16bc5e4c0dfdec3aadcb3556a7152707cfc28096 Mon Sep 17 00:00:00 2001 From: Jonathan Sheely Date: Sat, 25 Sep 2021 21:26:23 -0400 Subject: [PATCH 3/6] Added check if project container creation times out --- src/develop.ts | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/develop.ts b/src/develop.ts index d36639e..bde3fff 100755 --- a/src/develop.ts +++ b/src/develop.ts @@ -103,6 +103,10 @@ async function initialize({ sessionId ); + if (projectUrl == null) { + return; + } + const remoteFolderPath = `ssh://dappstarter@${projectUrl}:22//app`; await storeConfigurationFile(configFilePath, { @@ -161,13 +165,16 @@ async function reconnect({ const manifest = await checkForManifest(folderPath); const sessionId = v4(); setIsRemoteContainer(true); - await createRemoteContainer( + const status = await createRemoteContainer( projectName, publicKey, authKey, manifest, sessionId ); + if (status == null) { + return; + } if (!(await isSshOpen(projectUrl))) { return; } @@ -247,23 +254,31 @@ async function createRemoteContainer( ports: CUSTOM_PORTS ? PORTS : null, }, }); - await monitorContainerStatus(projectName, authKey); + const status = await monitorContainerStatus(projectName, authKey); clearInterval(timer); + if (status) { + spinner.stopAndPersist({ + symbol: emoji.get('heavy_check_mark'), + text: + spinner.text + + chalk.green( + `Container created: ${body.projectUrl.replace('.ssh', '')}` + ), + }); + return body; + } + spinner.stopAndPersist({ - symbol: emoji.get('heavy_check_mark'), + symbol: emoji.get('x'), text: spinner.text + - chalk.green( - `Container created: ${body.projectUrl.replace('.ssh', '')}` - ), + chalk.red(`Container creation timed out. Please try again`), }); - - return body; } async function monitorContainerStatus(projectName: string, authKey: string) { let timeout = timer(5 * 60 * 1000); - await interval(5000) + return !(await interval(5000) .pipe( startWith(0), map(() => @@ -277,7 +292,7 @@ async function monitorContainerStatus(projectName: string, authKey: string) { }), takeUntil(timeout) ) - .toPromise(); + .toPromise()); } async function checkContainerStatus( From c22a6ba8afbaf5a12a736426e82d14bd33b0b087 Mon Sep 17 00:00:00 2001 From: Jonathan Sheely Date: Mon, 27 Sep 2021 16:32:41 -0400 Subject: [PATCH 4/6] Handle reading and writing ssh config --- package.json | 1 + src/config.ts | 37 +++++++++++++++++++++++++------------ src/develop.subcommands.ts | 10 +++++++++- src/develop.ts | 2 +- yarn.lock | 5 +++++ 5 files changed, 41 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index ad2674d..b3c45fb 100755 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "rxjs": "^7.2.0", "shelljs": "^0.8.4", "slashes": "^2.0.2", + "ssh-config": "^4.0.6", "ssh2": "^1.4.0", "string-hash": "^1.1.3", "tar-stream": "^2.2.0", diff --git a/src/config.ts b/src/config.ts index 3c591a9..95f7b70 100644 --- a/src/config.ts +++ b/src/config.ts @@ -3,7 +3,8 @@ import { readFile, readJSON, writeJSON, - appendFileSync, + writeFile, + ensureFile, } from 'fs-extra'; import yaml from 'js-yaml'; import { log } from './utils'; @@ -12,6 +13,8 @@ import { DevelopConfig, DevelopConfigBase } from './types'; import { homedir } from 'os'; import { basename, join } from 'path'; import hash from 'string-hash'; +const SSHConfig = require('ssh-config'); + export const REQUEST_TIMEOUT: number = 10 * 1000; export const CONFIG_FILE = 'config.json'; export let SERVICE_URL = @@ -152,20 +155,30 @@ async function addHost({ projectName: string; projectUrl: string; }) { - const homeConfigDir = join(homedir(), '.ssh'); - const configFile = join(homeConfigDir, 'config'); + const sshConfigDir = join(homedir(), '.ssh'); + const configFile = join(sshConfigDir, 'config'); + await ensureFile(configFile); const config = await readFile(configFile, 'utf8'); + let sshConfig = SSHConfig.parse(config); // Check if host already exists if (!config.includes(projectUrl)) { - const host = `\nHost ${projectName} - HostName ${projectUrl} - User dappstarter - IdentityFile ${join(homeConfigDir, '.dappstarter', projectName, 'privatekey')} - ForwardAgent yes - ServerAliveInterval 15 - ServerAliveCountMax 4 - `; - await appendFileSync(configFile, host, { mode: 0o600 }); + sshConfig.append({ + Host: projectName, + User: 'dappstarter', + HostName: projectUrl, + IdentityFile: join( + homedir(), + '.dappstarter', + projectName, + 'privatekey' + ), + ForwardAgent: 'yes', + ServerAliveInterval: 15, + ServerAliveCountMax: 4, + }); + await writeFile(configFile, SSHConfig.stringify(sshConfig), { + mode: 0o600, + }); } } diff --git a/src/develop.subcommands.ts b/src/develop.subcommands.ts index c59a461..fd9305a 100644 --- a/src/develop.subcommands.ts +++ b/src/develop.subcommands.ts @@ -1,5 +1,5 @@ import chalk from 'chalk'; -import { pathExists, remove, readJson } from 'fs-extra'; +import { pathExists, remove, readJson, readFile, writeFile } from 'fs-extra'; import got from 'got'; import { REQUEST_TIMEOUT, SERVICE_URL, initPaths } from './config'; import { generateKeys } from './ssh'; @@ -10,6 +10,7 @@ import { IAuth, isAuthenticated } from './auth'; import { startContainer, stopContainer } from './docker'; import { optionSearch } from './utils'; import { Command } from 'commander'; +const SSHConfig = require('ssh-config'); export async function localAction(command: Command) { const inputDirectory = optionSearch(command, 'inputDirectory'); @@ -77,6 +78,13 @@ export async function cleanAction(command: Command) { projectName, }, }); + + const sshConfigDir = join(homedir(), '.ssh'); + const configFile = join(sshConfigDir, 'config'); + const config = await readFile(configFile, 'utf8'); + let sshConfig = SSHConfig.parse(config); + sshConfig.remove({ Host: projectName }); + await writeFile(configFile, SSHConfig.stringify(sshConfig)); if (pathExists(homeConfigDir)) { await remove(homeConfigDir); } diff --git a/src/develop.ts b/src/develop.ts index bde3fff..195f2a4 100755 --- a/src/develop.ts +++ b/src/develop.ts @@ -2,7 +2,6 @@ import { join } from 'path'; import { EventEmitter } from 'events'; import { ensureDir, readJSON, pathExists } from 'fs-extra'; import chalk from 'chalk'; -const Discovery = require('@decentology/node-discover'); import { connectable, defer, EMPTY, interval, timer } from 'rxjs'; import { catchError, @@ -36,6 +35,7 @@ import { } from './config'; import { Command } from 'commander'; import { v4 } from 'uuid'; +const Discovery = require('@decentology/node-discover'); const RemoteHostForwardingEV = new EventEmitter(); export default async function developAction(command: Command): Promise { diff --git a/yarn.lock b/yarn.lock index 5b19d7e..42b8388 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1885,6 +1885,11 @@ source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +ssh-config@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ssh-config/-/ssh-config-4.0.6.tgz#5cf104157d08702b47a112ae18075e433a6c5e86" + integrity sha512-GEtKJJ/R4XTImmBIGAfyw520rY7pS1MLjCPBcrPxp+431xSwMIU5iyCv98ua0nSXoR1B4qvOYJdTc7UvERzu8w== + ssh2@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.4.0.tgz#e32e8343394364c922bad915a5a7fecd67d0f5c5" From 0e6c3ea97b78f62871eb47946f2d7fe0df7f6617 Mon Sep 17 00:00:00 2001 From: Jonathan Sheely Date: Tue, 28 Sep 2021 09:15:23 -0400 Subject: [PATCH 5/6] Refactor remove host --- src/config.ts | 9 +++++++++ src/develop.subcommands.ts | 10 +++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/config.ts b/src/config.ts index 95f7b70..cd56111 100644 --- a/src/config.ts +++ b/src/config.ts @@ -182,3 +182,12 @@ async function addHost({ }); } } + +export async function removeHost(projectName: string) { + const sshConfigDir = join(homedir(), '.ssh'); + const configFile = join(sshConfigDir, 'config'); + const config = await readFile(configFile, 'utf8'); + let sshConfig = SSHConfig.parse(config); + sshConfig.remove({ Host: projectName }); + await writeFile(configFile, SSHConfig.stringify(sshConfig)); +} diff --git a/src/develop.subcommands.ts b/src/develop.subcommands.ts index fd9305a..e6af354 100644 --- a/src/develop.subcommands.ts +++ b/src/develop.subcommands.ts @@ -1,7 +1,7 @@ import chalk from 'chalk'; import { pathExists, remove, readJson, readFile, writeFile } from 'fs-extra'; import got from 'got'; -import { REQUEST_TIMEOUT, SERVICE_URL, initPaths } from './config'; +import { REQUEST_TIMEOUT, SERVICE_URL, initPaths, removeHost } from './config'; import { generateKeys } from './ssh'; import { homedir } from 'os'; import { join } from 'path'; @@ -50,6 +50,7 @@ export async function downAction(command: Command) { projectName, }, }); + await removeHost(projectName); console.log(chalk.blueBright(`Remote container has been stopped.`)); } catch (error) { console.error(chalk.red(JSON.stringify(error))); @@ -79,12 +80,7 @@ export async function cleanAction(command: Command) { }, }); - const sshConfigDir = join(homedir(), '.ssh'); - const configFile = join(sshConfigDir, 'config'); - const config = await readFile(configFile, 'utf8'); - let sshConfig = SSHConfig.parse(config); - sshConfig.remove({ Host: projectName }); - await writeFile(configFile, SSHConfig.stringify(sshConfig)); + await removeHost(projectName); if (pathExists(homeConfigDir)) { await remove(homeConfigDir); } From 539fc59eadd58c9f97996d1ae7ee9178e7d2f5a1 Mon Sep 17 00:00:00 2001 From: Jonathan Sheely Date: Tue, 28 Sep 2021 09:28:50 -0400 Subject: [PATCH 6/6] Re-add host on down and up --- src/config.ts | 2 +- src/develop.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/config.ts b/src/config.ts index cd56111..c265617 100644 --- a/src/config.ts +++ b/src/config.ts @@ -148,7 +148,7 @@ export async function getConfiguration( }; } -async function addHost({ +export async function addHost({ projectName, projectUrl, }: { diff --git a/src/develop.ts b/src/develop.ts index 195f2a4..11ccac1 100755 --- a/src/develop.ts +++ b/src/develop.ts @@ -32,6 +32,7 @@ import { setPrimaryHostProcess, PRIMARY_HOST_PROCESS, setIsRemoteContainer, + addHost, } from './config'; import { Command } from 'commander'; import { v4 } from 'uuid'; @@ -165,6 +166,7 @@ async function reconnect({ const manifest = await checkForManifest(folderPath); const sessionId = v4(); setIsRemoteContainer(true); + await addHost({ projectName, projectUrl }); const status = await createRemoteContainer( projectName, publicKey,