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 e9a104a..c265617 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,4 +1,11 @@ -import { pathExists, readFile, readJSON, writeJSON } from 'fs-extra'; +import { + pathExists, + readFile, + readJSON, + writeJSON, + writeFile, + ensureFile, +} from 'fs-extra'; import yaml from 'js-yaml'; import { log } from './utils'; import chalk from 'chalk'; @@ -6,11 +13,13 @@ 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 = -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,17 +126,68 @@ 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, }; } + +export async function addHost({ + projectName, + projectUrl, +}: { + projectName: string; + projectUrl: string; +}) { + 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)) { + 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, + }); + } +} + +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 c59a461..e6af354 100644 --- a/src/develop.subcommands.ts +++ b/src/develop.subcommands.ts @@ -1,7 +1,7 @@ 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 { REQUEST_TIMEOUT, SERVICE_URL, initPaths, removeHost } from './config'; import { generateKeys } from './ssh'; import { homedir } from 'os'; import { join } from 'path'; @@ -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'); @@ -49,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))); @@ -77,6 +79,8 @@ export async function cleanAction(command: Command) { projectName, }, }); + + await removeHost(projectName); if (pathExists(homeConfigDir)) { await remove(homeConfigDir); } diff --git a/src/develop.ts b/src/develop.ts index a27fd25..11ccac1 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, @@ -33,9 +32,11 @@ import { setPrimaryHostProcess, PRIMARY_HOST_PROCESS, setIsRemoteContainer, + addHost, } 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 { @@ -103,10 +104,15 @@ async function initialize({ sessionId ); + if (projectUrl == null) { + return; + } + const remoteFolderPath = `ssh://dappstarter@${projectUrl}:22//app`; await storeConfigurationFile(configFilePath, { projectUrl, + projectName, }); if (!(await isSshOpen(projectUrl))) { @@ -160,13 +166,17 @@ async function reconnect({ const manifest = await checkForManifest(folderPath); const sessionId = v4(); setIsRemoteContainer(true); - await createRemoteContainer( + await addHost({ projectName, projectUrl }); + const status = await createRemoteContainer( projectName, publicKey, authKey, manifest, sessionId ); + if (status == null) { + return; + } if (!(await isSshOpen(projectUrl))) { return; } @@ -246,23 +256,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(() => @@ -276,7 +294,7 @@ async function monitorContainerStatus(projectName: string, authKey: string) { }), takeUntil(timeout) ) - .toPromise(); + .toPromise()); } async function checkContainerStatus( 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 = { 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"