feat: add ssh connection retry

This commit is contained in:
Justin Lin committed 2024-10-14 17:31:35 +08:00
1 parent 251139c159
commit 31301419ad
4 files changed
+64 -44

No files matched your search

+1 -1
View File
@@ -1,11 +1,11 @@
HOST=localhost HOST=localhost
PORT=3000 PORT=3000
BASE_URL=http://localhost:5173 BASE_URL=http://localhost:5173
BACKEND_URL=http://localhost:3000
MONGO_URL=mongodb://localhost:27017/odyssey-test MONGO_URL=mongodb://localhost:27017/odyssey-test
SECRET_KEY=sample-secret SECRET_KEY=sample-secret
# Optional # Optional
# BACKEND_URL=http://host.docker.internal:3000
# CONTAINER_EXPIRY=60 # 60 minutes # CONTAINER_EXPIRY=60 # 60 minutes
# SESSION_MAX_AGE=168 # 7 days # SESSION_MAX_AGE=168 # 7 days
# TRUSTED_PROXIES=1.2.3.4 # TRUSTED_PROXIES=1.2.3.4
+2 -2
View File
@@ -35,6 +35,7 @@ function createConfig() {
const host = get('HOST', '0.0.0.0') const host = get('HOST', '0.0.0.0')
const port = Number(get('PORT', 3000)) const port = Number(get('PORT', 3000))
const baseUrl = get('BASE_URL', `http://${host}:${port}`) const baseUrl = get('BASE_URL', `http://${host}:${port}`)
const backendUrl = get('BACKEND_URL', `http://host.docker.internal:${port}`)
const url = new URL(baseUrl) const url = new URL(baseUrl)
const projectRoot = getProjectRoot() const projectRoot = getProjectRoot()
const keypairPath = get( const keypairPath = get(
@@ -49,7 +50,7 @@ function createConfig() {
baseUrl, baseUrl,
domain: url.hostname, domain: url.hostname,
protocol: url.protocol, protocol: url.protocol,
backendUrl: get('BACKEND_URL', baseUrl), backendUrl,
db: get('MONGO_URL', 'mongodb://localhost:27017/odyssey-test'), db: get('MONGO_URL', 'mongodb://localhost:27017/odyssey-test'),
secret: get('SECRET_KEY', ''), secret: get('SECRET_KEY', ''),
isProduction: process.env.NODE_ENV === 'production', isProduction: process.env.NODE_ENV === 'production',
@@ -97,7 +98,6 @@ function getProjectRoot(): string {
} }
const config = createConfig() const config = createConfig()
console.log(config.docker.keypair)
export function getQuestImage(id: string): string { export function getQuestImage(id: string): string {
return `${config.docker.imagePrefix}${id}` return `${config.docker.imagePrefix}${id}`
+13 -41
View File
@@ -1,9 +1,9 @@
import fs from 'fs/promises' import fs from 'fs/promises'
import { Duplex } from 'stream' import { Duplex } from 'stream'
import { Client } from 'ssh2'
import Docker from 'dockerode' import Docker from 'dockerode'
import config, { getQuestImage } from '../config.js' import config, { getQuestImage } from '../config.js'
import logger from '../utils/logger.js' import logger from '../utils/logger.js'
import { connectToSSH } from './ssh.js'
const engine = new Docker() const engine = new Docker()
@@ -70,9 +70,6 @@ export async function getAndStartContainer(
if (!(await container.inspect()).State.Running) { if (!(await container.inspect()).State.Running) {
await container.start() await container.start()
} }
await new Promise((resolve) => {
setTimeout(resolve, 2000)
})
return container return container
} }
@@ -83,45 +80,20 @@ export async function attachContainer(
const containerIp = (await container.inspect()).NetworkSettings.Networks[ const containerIp = (await container.inspect()).NetworkSettings.Networks[
config.docker.network config.docker.network
].IPAddress ].IPAddress
const conn = new Client()
console.log(config.docker.keypair.privateKey)
return new Promise((resolve, reject) => { for (let i = 0; i < 10; i++) {
conn try {
.on('ready', () => { // eslint-disable-next-line no-await-in-loop
console.log('SSH connection ready') const stream = await connectToSSH(containerIp, { token })
conn.shell( return stream
{ } catch {
env: { // eslint-disable-next-line no-await-in-loop
TOKEN: token, await new Promise((resolve) => {
API_ENDPOINT: 'http://host.docker.internal:3000', setTimeout(resolve, 1000)
ZDOTDIR: '/etc/zsh',
NODE_PATH: '/usr/src/node_modules',
},
},
(err, stream) => {
if (err) {
reject(err)
}
stream.on('close', () => {
console.log('Stream :: close')
conn.end()
})
resolve(stream)
}
)
}) })
.on('error', (err) => { }
reject(err) }
}) throw new Error('Failed to connect to SSH')
.connect({
host: containerIp,
port: 22,
username: 'commander',
privateKey: config.docker.keypair.privateKey,
debug: console.log,
})
})
} }
export async function deleteContainer(id: string) { export async function deleteContainer(id: string) {
+48
View File
@@ -0,0 +1,48 @@
import { Client } from 'ssh2'
import { Duplex } from 'stream'
import config from '../config.js'
export async function connectToSSH(
containerIp: string,
{ token }: { token: string }
): Promise<Duplex> {
const conn = new Client()
return new Promise((resolve, reject) => {
conn
.on('ready', () => {
console.log('SSH connection ready')
conn.shell(
{
env: {
TOKEN: token,
API_ENDPOINT: config.backendUrl,
ZDOTDIR: '/etc/zsh',
NODE_PATH: '/usr/src/node_modules',
},
},
(err, stream) => {
if (err) {
reject(err)
}
stream.on('close', () => {
console.log('Stream :: close')
conn.end()
})
resolve(stream)
}
)
})
.on('error', (err) => {
reject(err)
})
.connect({
host: containerIp,
port: 22,
username: 'commander',
privateKey: config.docker.keypair.privateKey,
debug: console.log,
timeout: 5000,
})
})
}