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')
}