mirror of
https://github.com/linux-odyssey/linux-odyssey.git
synced 2026-11-05 19:20:12 +08:00
feat: add ssh connection retry
This commit is contained in:
1 parent
251139c159
commit
31301419ad
4 files changed
+64
-44
No files matched your search
+1
-1
@@ -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
|
||||||
|
|||||||
@@ -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}`
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in new issue
Block a user