228 lines
5.2 KiB
TypeScript
228 lines
5.2 KiB
TypeScript
/**
|
|
* Docker 管理器
|
|
*
|
|
* 管理 Docker 镜像、容器和服务编排
|
|
*/
|
|
|
|
import { DeviceConnection, DeploymentResult } from './types'
|
|
import { executeRemoteCommand } from './connection'
|
|
|
|
interface ComposeServiceRow {
|
|
Name?: string
|
|
Service?: string
|
|
State?: string
|
|
Status?: string
|
|
Health?: string
|
|
}
|
|
|
|
function parseComposeServices(output: string): ComposeServiceRow[] {
|
|
return output
|
|
.trim()
|
|
.split('\n')
|
|
.map((line): ComposeServiceRow | null => {
|
|
try {
|
|
const parsed = JSON.parse(line) as ComposeServiceRow
|
|
return parsed
|
|
} catch {
|
|
return null
|
|
}
|
|
})
|
|
.filter((service): service is ComposeServiceRow => service !== null)
|
|
}
|
|
|
|
/**
|
|
* 检查 Docker 是否安装
|
|
*/
|
|
export async function checkDockerInstalled(
|
|
connection: DeviceConnection
|
|
): Promise<boolean> {
|
|
try {
|
|
const result = await executeRemoteCommand(connection, 'docker --version')
|
|
return result.exitCode === 0
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 检查 Docker Compose 是否安装
|
|
*/
|
|
export async function checkDockerComposeInstalled(
|
|
connection: DeviceConnection
|
|
): Promise<boolean> {
|
|
try {
|
|
const result = await executeRemoteCommand(connection, 'docker-compose --version')
|
|
return result.exitCode === 0
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 启动 Docker 服务
|
|
*/
|
|
export async function startDockerServices(
|
|
connection: DeviceConnection,
|
|
_composeFile: string
|
|
): Promise<DeploymentResult> {
|
|
try {
|
|
// 上传 docker-compose.yml 和 .env 文件
|
|
// TODO: 实现文件上传
|
|
|
|
// 启动服务
|
|
const result = await executeRemoteCommand(
|
|
connection,
|
|
'docker-compose up -d'
|
|
)
|
|
|
|
if (result.exitCode !== 0) {
|
|
return {
|
|
success: false,
|
|
message: '启动 Docker 服务失败',
|
|
errors: [result.stderr],
|
|
}
|
|
}
|
|
|
|
// 等待服务健康检查
|
|
await waitForServicesHealthy(connection)
|
|
|
|
return {
|
|
success: true,
|
|
message: '服务启动成功',
|
|
accessUrl: `http://${connection.host}:3000`,
|
|
adminUsername: 'admin',
|
|
}
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
message: '部署失败',
|
|
errors: [error instanceof Error ? error.message : String(error)],
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 等待服务健康检查通过
|
|
*/
|
|
async function waitForServicesHealthy(
|
|
connection: DeviceConnection,
|
|
timeout: number = 120000
|
|
): Promise<void> {
|
|
const startTime = Date.now()
|
|
|
|
while (Date.now() - startTime < timeout) {
|
|
try {
|
|
const result = await executeRemoteCommand(
|
|
connection,
|
|
'docker-compose ps --format json'
|
|
)
|
|
|
|
if (result.exitCode === 0 && result.stdout.trim()) {
|
|
// 解析 JSON 输出
|
|
const services = parseComposeServices(result.stdout)
|
|
|
|
// 检查所有服务是否都在运行
|
|
const allRunning = services.every((service) =>
|
|
service.State === 'running' || service.State === 'Up'
|
|
)
|
|
|
|
// 检查是否有健康检查
|
|
const servicesWithHealth = services.filter((service) =>
|
|
service.Health !== undefined
|
|
)
|
|
|
|
// 如果有健康检查,确保都是 healthy
|
|
const allHealthy = servicesWithHealth.length === 0 ||
|
|
servicesWithHealth.every((service) =>
|
|
service.Health === 'healthy'
|
|
)
|
|
|
|
if (allRunning && allHealthy && services.length > 0) {
|
|
return // 所有服务都健康
|
|
}
|
|
}
|
|
} catch {
|
|
// 忽略错误,继续重试
|
|
}
|
|
|
|
// 等待 5 秒后重试
|
|
await new Promise(resolve => setTimeout(resolve, 5000))
|
|
}
|
|
|
|
throw new Error('服务健康检查超时')
|
|
}
|
|
|
|
/**
|
|
* 停止 Docker 服务
|
|
*/
|
|
export async function stopDockerServices(
|
|
connection: DeviceConnection
|
|
): Promise<void> {
|
|
await executeRemoteCommand(connection, 'docker-compose down')
|
|
}
|
|
|
|
/**
|
|
* 获取服务日志
|
|
*/
|
|
export async function getServiceLogs(
|
|
connection: DeviceConnection,
|
|
serviceName?: string
|
|
): Promise<string> {
|
|
const command = serviceName
|
|
? `docker-compose logs ${serviceName}`
|
|
: 'docker-compose logs'
|
|
|
|
const result = await executeRemoteCommand(connection, command)
|
|
return result.stdout
|
|
}
|
|
|
|
/**
|
|
* 获取服务状态
|
|
*/
|
|
export async function getServicesStatus(
|
|
connection: DeviceConnection
|
|
): Promise<Array<{ name: string; status: string; health: string }>> {
|
|
try {
|
|
const result = await executeRemoteCommand(
|
|
connection,
|
|
'docker-compose ps --format json'
|
|
)
|
|
|
|
if (result.exitCode === 0 && result.stdout.trim()) {
|
|
// 解析 JSON 输出
|
|
const services = parseComposeServices(result.stdout).map((service) => ({
|
|
name: service.Name || service.Service || 'unknown',
|
|
status: service.State || service.Status || 'unknown',
|
|
health: service.Health || 'none',
|
|
}))
|
|
|
|
return services
|
|
}
|
|
} catch {
|
|
// 返回空数组
|
|
}
|
|
|
|
return []
|
|
}
|
|
|
|
/**
|
|
* 拉取 Docker 镜像
|
|
*/
|
|
export async function pullDockerImages(
|
|
connection: DeviceConnection,
|
|
images: string[]
|
|
): Promise<void> {
|
|
for (const image of images) {
|
|
await executeRemoteCommand(connection, `docker pull ${image}`)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 清理未使用的 Docker 资源
|
|
*/
|
|
export async function cleanupDockerResources(
|
|
connection: DeviceConnection
|
|
): Promise<void> {
|
|
await executeRemoteCommand(connection, 'docker system prune -f')
|
|
}
|