First
Some checks failed
Build All Docker Images / changes (push) Has been cancelled
Build and Push App Docker Image / build (push) Has been cancelled
Build and Push Node Docker Image / build (push) Has been cancelled
Test and Lint / test-app (push) Has been cancelled
Test and Lint / test-node (push) Has been cancelled
Test and Lint / lint-dockerfiles (push) Has been cancelled
Test and Lint / security-scan (push) Has been cancelled
Build All Docker Images / build-app (push) Has been cancelled
Build All Docker Images / build-node (push) Has been cancelled
Build All Docker Images / summary (push) Has been cancelled
Some checks failed
Build All Docker Images / changes (push) Has been cancelled
Build and Push App Docker Image / build (push) Has been cancelled
Build and Push Node Docker Image / build (push) Has been cancelled
Test and Lint / test-app (push) Has been cancelled
Test and Lint / test-node (push) Has been cancelled
Test and Lint / lint-dockerfiles (push) Has been cancelled
Test and Lint / security-scan (push) Has been cancelled
Build All Docker Images / build-app (push) Has been cancelled
Build All Docker Images / build-node (push) Has been cancelled
Build All Docker Images / summary (push) Has been cancelled
This commit is contained in:
commit
4169337dd0
68 changed files with 8726 additions and 0 deletions
247
node/src/services/dockerManager.ts
Normal file
247
node/src/services/dockerManager.ts
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
import Docker from 'dockerode';
|
||||
import { logger } from '../utils/logger.js';
|
||||
|
||||
export interface GameServerConfig {
|
||||
name: string;
|
||||
image: string;
|
||||
ports: { [key: string]: number };
|
||||
environment?: { [key: string]: string };
|
||||
volumes?: string[];
|
||||
restart?: string;
|
||||
}
|
||||
|
||||
export interface ServerStatus {
|
||||
name: string;
|
||||
status: 'running' | 'stopped' | 'not-found';
|
||||
containerId?: string;
|
||||
ports?: { [key: string]: number };
|
||||
uptime?: string;
|
||||
}
|
||||
|
||||
export class DockerManager {
|
||||
private docker: Docker;
|
||||
private gameServers: Map<string, GameServerConfig>;
|
||||
|
||||
constructor() {
|
||||
this.docker = new Docker();
|
||||
this.gameServers = new Map();
|
||||
this.initializeGameServers();
|
||||
}
|
||||
|
||||
private initializeGameServers() {
|
||||
// Define available game servers
|
||||
const servers: GameServerConfig[] = [
|
||||
{
|
||||
name: 'minecraft',
|
||||
image: 'itzg/minecraft-server:latest',
|
||||
ports: { '25565': 25565 },
|
||||
environment: {
|
||||
EULA: 'TRUE',
|
||||
TYPE: 'VANILLA',
|
||||
MEMORY: '2G'
|
||||
},
|
||||
volumes: ['/data/minecraft:/data'],
|
||||
restart: 'unless-stopped'
|
||||
},
|
||||
{
|
||||
name: 'valheim',
|
||||
image: 'lloesche/valheim-server:latest',
|
||||
ports: { '2456': 2456, '2457': 2457 },
|
||||
environment: {
|
||||
SERVER_NAME: 'My Valheim Server',
|
||||
WORLD_NAME: 'MyWorld',
|
||||
SERVER_PASS: 'secret123'
|
||||
},
|
||||
volumes: ['/data/valheim:/config'],
|
||||
restart: 'unless-stopped'
|
||||
},
|
||||
{
|
||||
name: 'terraria',
|
||||
image: 'ryshe/terraria:latest',
|
||||
ports: { '7777': 7777 },
|
||||
environment: {
|
||||
WORLD: 'MyWorld',
|
||||
PASSWORD: 'secret123'
|
||||
},
|
||||
volumes: ['/data/terraria:/world'],
|
||||
restart: 'unless-stopped'
|
||||
}
|
||||
];
|
||||
|
||||
servers.forEach(server => {
|
||||
this.gameServers.set(server.name, server);
|
||||
});
|
||||
}
|
||||
|
||||
async getRunningContainers(): Promise<any[]> {
|
||||
try {
|
||||
const containers = await this.docker.listContainers();
|
||||
return containers;
|
||||
} catch (error) {
|
||||
logger.error('Error listing containers:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async getServerStatus(serviceName: string): Promise<ServerStatus> {
|
||||
try {
|
||||
const containers = await this.docker.listContainers({ all: true });
|
||||
const container = containers.find(c =>
|
||||
c.Names.some(name => name.includes(serviceName)) ||
|
||||
c.Labels?.['game-server'] === serviceName
|
||||
);
|
||||
|
||||
if (!container) {
|
||||
return {
|
||||
name: serviceName,
|
||||
status: 'not-found'
|
||||
};
|
||||
}
|
||||
|
||||
const status = container.State === 'running' ? 'running' : 'stopped';
|
||||
const ports: { [key: string]: number } = {};
|
||||
|
||||
if (container.Ports) {
|
||||
container.Ports.forEach(port => {
|
||||
if (port.PublicPort) {
|
||||
ports[port.PrivatePort.toString()] = port.PublicPort;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
name: serviceName,
|
||||
status,
|
||||
containerId: container.Id,
|
||||
ports,
|
||||
uptime: status === 'running' ? this.calculateUptime(container.Created) : undefined
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(`Error getting status for ${serviceName}:`, error);
|
||||
return {
|
||||
name: serviceName,
|
||||
status: 'not-found'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async startServer(serviceName: string): Promise<{ success: boolean; message: string }> {
|
||||
try {
|
||||
const config = this.gameServers.get(serviceName);
|
||||
if (!config) {
|
||||
return { success: false, message: `Unknown game server: ${serviceName}` };
|
||||
}
|
||||
|
||||
// Check if container already exists
|
||||
const status = await this.getServerStatus(serviceName);
|
||||
if (status.status === 'running') {
|
||||
return { success: false, message: `${serviceName} is already running` };
|
||||
}
|
||||
|
||||
if (status.containerId) {
|
||||
// Container exists but is stopped, start it
|
||||
const container = this.docker.getContainer(status.containerId);
|
||||
await container.start();
|
||||
logger.info(`Started existing container for ${serviceName}`);
|
||||
} else {
|
||||
// Create new container
|
||||
const containerOptions = {
|
||||
Image: config.image,
|
||||
name: `gameserver-${serviceName}`,
|
||||
Labels: {
|
||||
'game-server': serviceName
|
||||
},
|
||||
Env: config.environment ? Object.entries(config.environment).map(([k, v]) => `${k}=${v}`) : [],
|
||||
ExposedPorts: Object.keys(config.ports).reduce((acc, port) => {
|
||||
acc[`${port}/tcp`] = {};
|
||||
return acc;
|
||||
}, {} as any),
|
||||
HostConfig: {
|
||||
PortBindings: Object.entries(config.ports).reduce((acc, [privatePort, publicPort]) => {
|
||||
acc[`${privatePort}/tcp`] = [{ HostPort: publicPort.toString() }];
|
||||
return acc;
|
||||
}, {} as any),
|
||||
Binds: config.volumes || [],
|
||||
RestartPolicy: {
|
||||
Name: config.restart || 'unless-stopped'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const container = await this.docker.createContainer(containerOptions);
|
||||
await container.start();
|
||||
logger.info(`Created and started new container for ${serviceName}`);
|
||||
}
|
||||
|
||||
return { success: true, message: `${serviceName} started successfully` };
|
||||
} catch (error) {
|
||||
logger.error(`Error starting ${serviceName}:`, error);
|
||||
return { success: false, message: `Failed to start ${serviceName}: ${error}` };
|
||||
}
|
||||
}
|
||||
|
||||
async stopServer(serviceName: string): Promise<{ success: boolean; message: string }> {
|
||||
try {
|
||||
const status = await this.getServerStatus(serviceName);
|
||||
if (status.status !== 'running') {
|
||||
return { success: false, message: `${serviceName} is not running` };
|
||||
}
|
||||
|
||||
if (status.containerId) {
|
||||
const container = this.docker.getContainer(status.containerId);
|
||||
await container.stop();
|
||||
logger.info(`Stopped container for ${serviceName}`);
|
||||
return { success: true, message: `${serviceName} stopped successfully` };
|
||||
}
|
||||
|
||||
return { success: false, message: `Could not find container for ${serviceName}` };
|
||||
} catch (error) {
|
||||
logger.error(`Error stopping ${serviceName}:`, error);
|
||||
return { success: false, message: `Failed to stop ${serviceName}: ${error}` };
|
||||
}
|
||||
}
|
||||
|
||||
async listGameServers(): Promise<{ available: string[]; running: ServerStatus[] }> {
|
||||
const available = Array.from(this.gameServers.keys());
|
||||
const running: ServerStatus[] = [];
|
||||
|
||||
for (const serverName of available) {
|
||||
const status = await this.getServerStatus(serverName);
|
||||
if (status.status === 'running') {
|
||||
running.push(status);
|
||||
}
|
||||
}
|
||||
|
||||
return { available, running };
|
||||
}
|
||||
|
||||
async getSystemStats(): Promise<any> {
|
||||
try {
|
||||
const info = await this.docker.info();
|
||||
const version = await this.docker.version();
|
||||
return {
|
||||
dockerVersion: version.Version,
|
||||
containers: info.Containers,
|
||||
containersRunning: info.ContainersRunning,
|
||||
containersPaused: info.ContainersPaused,
|
||||
containersStopped: info.ContainersStopped,
|
||||
images: info.Images,
|
||||
memoryLimit: info.MemoryLimit,
|
||||
swapLimit: info.SwapLimit,
|
||||
cpus: info.NCPU,
|
||||
architecture: info.Architecture
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Error getting system stats:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private calculateUptime(created: number): string {
|
||||
const now = Date.now();
|
||||
const uptime = now - (created * 1000);
|
||||
const hours = Math.floor(uptime / (1000 * 60 * 60));
|
||||
const minutes = Math.floor((uptime % (1000 * 60 * 60)) / (1000 * 60));
|
||||
return `${hours}h ${minutes}m`;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue