113 lines
4.0 KiB
TypeScript
113 lines
4.0 KiB
TypeScript
import * as fs from 'node:fs/promises';
|
|
import { PATHS, VERSION } from '../constants.ts';
|
|
import type { IModelGridConfig } from '../interfaces/config.ts';
|
|
import { logger } from '../logger.ts';
|
|
|
|
export class ConfigManager {
|
|
public async loadConfig(): Promise<IModelGridConfig> {
|
|
try {
|
|
const configContent = await fs.readFile(PATHS.CONFIG_FILE, 'utf-8');
|
|
return this.normalizeConfig(JSON.parse(configContent) as Partial<IModelGridConfig>);
|
|
} catch (error) {
|
|
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
throw new Error(`Configuration file not found: ${PATHS.CONFIG_FILE}`);
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
public async saveConfig(config: IModelGridConfig): Promise<void> {
|
|
await fs.mkdir(PATHS.CONFIG_DIR, { recursive: true });
|
|
await fs.writeFile(PATHS.CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
}
|
|
|
|
public normalizeConfig(config: Partial<IModelGridConfig>): IModelGridConfig {
|
|
this.logIgnoredConfigKeys(config);
|
|
|
|
const filteredContainers = (config.containers || []).filter(
|
|
(container) => (container as { type?: string }).type !== 'ollama',
|
|
);
|
|
|
|
return {
|
|
version: config.version || VERSION,
|
|
api: {
|
|
port: config.api?.port || 8080,
|
|
host: config.api?.host || '0.0.0.0',
|
|
apiKeys: config.api?.apiKeys || [],
|
|
rateLimit: config.api?.rateLimit,
|
|
cors: config.api?.cors ?? true,
|
|
corsOrigins: config.api?.corsOrigins || ['*'],
|
|
},
|
|
ui: {
|
|
enabled: config.ui?.enabled ?? true,
|
|
port: config.ui?.port || 8081,
|
|
host: config.ui?.host || '0.0.0.0',
|
|
assetSource: config.ui?.assetSource === 'disk' ? 'disk' : 'bundle',
|
|
},
|
|
docker: {
|
|
networkName: config.docker?.networkName || 'modelgrid',
|
|
runtime: config.docker?.runtime || 'docker',
|
|
socketPath: config.docker?.socketPath,
|
|
},
|
|
gpus: {
|
|
autoDetect: config.gpus?.autoDetect ?? true,
|
|
assignments: config.gpus?.assignments || {},
|
|
},
|
|
containers: filteredContainers,
|
|
models: {
|
|
registryUrl: config.models?.registryUrl || 'https://list.modelgrid.com/catalog/models.json',
|
|
autoDeploy: config.models?.autoDeploy ?? true,
|
|
defaultEngine: 'vllm',
|
|
autoLoad: config.models?.autoLoad || [],
|
|
},
|
|
cluster: {
|
|
enabled: config.cluster?.enabled ?? false,
|
|
nodeName: config.cluster?.nodeName || 'modelgrid-local',
|
|
role: config.cluster?.role || 'standalone',
|
|
bindHost: config.cluster?.bindHost || '0.0.0.0',
|
|
gossipPort: config.cluster?.gossipPort || 7946,
|
|
sharedSecret: config.cluster?.sharedSecret,
|
|
advertiseUrl: config.cluster?.advertiseUrl,
|
|
controlPlaneUrl: config.cluster?.controlPlaneUrl,
|
|
heartbeatIntervalMs: config.cluster?.heartbeatIntervalMs || 5000,
|
|
seedNodes: config.cluster?.seedNodes || [],
|
|
},
|
|
checkInterval: config.checkInterval || 30000,
|
|
};
|
|
}
|
|
|
|
private logIgnoredConfigKeys(config: Partial<IModelGridConfig>): void {
|
|
const unknownTopLevelKeys = Object.keys(config).filter((key) =>
|
|
!['version', 'api', 'ui', 'docker', 'gpus', 'containers', 'models', 'cluster', 'checkInterval']
|
|
.includes(key)
|
|
);
|
|
|
|
for (const key of unknownTopLevelKeys) {
|
|
logger.warn(`Ignoring unknown config key: ${key}`);
|
|
}
|
|
|
|
const legacyModelConfig = config.models as {
|
|
greenlistUrl?: string;
|
|
autoPull?: boolean;
|
|
defaultContainer?: string;
|
|
} | undefined;
|
|
|
|
if (legacyModelConfig?.greenlistUrl) {
|
|
logger.warn('Ignoring removed config key: models.greenlistUrl');
|
|
}
|
|
if (legacyModelConfig?.autoPull !== undefined) {
|
|
logger.warn('Ignoring removed config key: models.autoPull');
|
|
}
|
|
if (legacyModelConfig?.defaultContainer) {
|
|
logger.warn('Ignoring removed config key: models.defaultContainer');
|
|
}
|
|
|
|
for (const container of config.containers || []) {
|
|
const containerType = (container as { type?: string }).type;
|
|
if (containerType === 'ollama') {
|
|
logger.warn('Ignoring unsupported container type: ollama');
|
|
}
|
|
}
|
|
}
|
|
}
|