Files
dockhand/src/routes/api/system/+server.ts
T
2026-03-02 07:59:58 +01:00

235 lines
7.0 KiB
TypeScript

import { json } from '@sveltejs/kit';
import {
getDockerInfo,
getDockerVersion,
listContainers,
listImages,
listVolumes,
listNetworks,
getDockerConnectionInfo
} from '$lib/server/docker';
import { getStackSources } from '$lib/server/db';
import { isPostgres, isSqlite, getDatabaseSchemaVersion, getPostgresConnectionInfo } from '$lib/server/db/drizzle';
import { hasEnvironments } from '$lib/server/db';
import type { RequestHandler } from './$types';
import { existsSync, readFileSync } from 'node:fs';
import * as http from 'node:http';
import os from 'node:os';
import { authorize } from '$lib/server/authorize';
// Detect if running inside a Docker container
function detectContainerRuntime(): { inContainer: boolean; runtime?: string; containerId?: string } {
// Check for .dockerenv file (Docker)
if (existsSync('/.dockerenv')) {
let containerId: string | undefined;
try {
// Try to get container ID from hostname (Docker sets it)
containerId = os.hostname();
// Validate it looks like a container ID (12+ hex chars)
if (!/^[a-f0-9]{12,}$/i.test(containerId)) {
containerId = undefined;
}
} catch {}
return { inContainer: true, runtime: 'docker', containerId };
}
// Check cgroup for container indicators
try {
if (existsSync('/proc/1/cgroup')) {
const cgroup = readFileSync('/proc/1/cgroup', 'utf-8');
if (cgroup.includes('docker') || cgroup.includes('containerd') || cgroup.includes('kubepods')) {
const runtime = cgroup.includes('kubepods') ? 'kubernetes' :
cgroup.includes('containerd') ? 'containerd' : 'docker';
return { inContainer: true, runtime };
}
}
} catch {}
return { inContainer: false };
}
// Get runtime info
function getRuntimeInfo() {
const memUsage = process.memoryUsage();
return {
name: 'Node.js',
version: process.version,
memory: {
heapUsed: memUsage.heapUsed,
heapTotal: memUsage.heapTotal,
rss: memUsage.rss,
external: memUsage.external
}
};
}
// Get info about our own container if running in Docker
async function getOwnContainerInfo(containerId: string | undefined): Promise<any> {
if (!containerId) return null;
try {
const socketPaths = [
'/var/run/docker.sock',
process.env.DOCKER_SOCKET
].filter(Boolean);
for (const socketPath of socketPaths) {
if (!socketPath || !existsSync(socketPath)) continue;
try {
const info = await new Promise<any>((resolve, reject) => {
const req = http.request({
socketPath,
path: `/containers/${containerId}/json`,
method: 'GET',
}, (res) => {
const chunks: Buffer[] = [];
res.on('data', (chunk: Buffer) => chunks.push(chunk));
res.on('end', () => {
if (res.statusCode === 200) {
resolve(JSON.parse(Buffer.concat(chunks).toString('utf-8')));
} else {
resolve(null);
}
});
res.on('error', () => resolve(null));
});
req.on('error', () => resolve(null));
req.end();
});
if (info) {
return {
id: info.Id?.slice(0, 12),
name: info.Name?.replace(/^\//, ''),
image: info.Config?.Image,
imageId: info.Image?.slice(7, 19),
created: info.Created,
status: info.State?.Status,
restartCount: info.RestartCount,
labels: {
version: info.Config?.Labels?.['org.opencontainers.image.version'],
revision: info.Config?.Labels?.['org.opencontainers.image.revision']
}
};
}
} catch {}
}
} catch {}
return null;
}
export const GET: RequestHandler = async ({ url, cookies }) => {
const auth = await authorize(cookies);
// Check basic environment view permission
if (auth.authEnabled && !await auth.can('environments', 'view')) {
return json({ error: 'Permission denied' }, { status: 403 });
}
try {
const envId = url.searchParams.get('env') ? parseInt(url.searchParams.get('env')!) : null;
// Check environment access in enterprise mode
if (envId && auth.authEnabled && auth.isEnterprise && !await auth.canAccessEnvironment(envId)) {
return json({ error: 'Access denied to this environment' }, { status: 403 });
}
const schemaVersion = await getDatabaseSchemaVersion();
// Try to get Docker info, but don't fail if Docker isn't available
let dockerInfo = null;
let dockerVersion = null;
let connectionInfo = null;
let containers: any[] = [];
let images: any[] = [];
let volumes: any[] = [];
let networks: any[] = [];
// Only try Docker connection if environment is specified
if (envId) {
try {
[dockerInfo, dockerVersion, containers, images, volumes, networks, connectionInfo] = await Promise.all([
getDockerInfo(envId),
getDockerVersion(envId),
listContainers(true, envId),
listImages(envId),
listVolumes(envId),
listNetworks(envId),
getDockerConnectionInfo(envId)
]);
} catch (dockerError) {
// Docker not available - continue with null values
console.log('Docker not available for system info:', dockerError instanceof Error ? dockerError.message : String(dockerError));
}
}
const stacks = await getStackSources();
const runningContainers = containers.filter(c => c.state === 'running').length;
const stoppedContainers = containers.length - runningContainers;
const runtimeInfo = getRuntimeInfo();
const containerRuntime = detectContainerRuntime();
const ownContainer = containerRuntime.inContainer
? await getOwnContainerInfo(containerRuntime.containerId || os.hostname())
: null;
return json({
docker: dockerInfo && dockerVersion ? {
version: dockerVersion.Version,
apiVersion: dockerVersion.ApiVersion,
os: dockerInfo.OperatingSystem,
arch: dockerInfo.Architecture,
kernelVersion: dockerInfo.KernelVersion,
serverVersion: dockerInfo.ServerVersion,
connection: connectionInfo ? {
type: connectionInfo.type,
socketPath: connectionInfo.socketPath,
host: connectionInfo.host,
port: connectionInfo.port
} : { type: 'socket' }
} : null,
host: dockerInfo ? {
name: dockerInfo.Name,
cpus: dockerInfo.NCPU,
memory: dockerInfo.MemTotal,
storageDriver: dockerInfo.Driver
} : null,
runtime: {
runtimeName: runtimeInfo.name,
runtimeVersion: runtimeInfo.version,
nodeVersion: runtimeInfo.version,
platform: os.platform(),
arch: os.arch(),
kernel: os.release(),
memory: runtimeInfo.memory,
container: containerRuntime,
ownContainer
},
database: {
type: isPostgres ? 'PostgreSQL' : 'SQLite',
schemaVersion: schemaVersion.version,
schemaDate: schemaVersion.date,
...(isPostgres && getPostgresConnectionInfo() ? {
host: getPostgresConnectionInfo()!.host,
port: getPostgresConnectionInfo()!.port
} : {})
},
stats: {
containers: {
total: containers.length,
running: runningContainers,
stopped: stoppedContainers
},
images: images.length,
volumes: volumes.length,
networks: networks.length,
stacks: stacks.length
}
});
} catch (error) {
console.error('Error fetching system info:', error);
return json({ error: 'Failed to fetch system info' }, { status: 500 });
}
};