f40ef6b7c0
Align Cloudly with the current typedserver, smartconfig, smartstate, and Docker tooling releases so builds and Docker output stay compatible with the upgraded stack.
299 lines
10 KiB
TypeScript
299 lines
10 KiB
TypeScript
import * as plugins from '../plugins.js';
|
|
import type { Cloudly } from '../classes.cloudly.js';
|
|
import * as servezoneInterfaces from '@serve.zone/interfaces';
|
|
|
|
export class CloudlySettingsManager {
|
|
public cloudlyRef: Cloudly;
|
|
public readyDeferred = plugins.smartpromise.defer();
|
|
public settingsStore!: plugins.smartdata.EasyStore<servezoneInterfaces.data.ICloudlySettings>;
|
|
|
|
constructor(cloudlyRefArg: Cloudly) {
|
|
this.cloudlyRef = cloudlyRefArg;
|
|
}
|
|
|
|
/**
|
|
* Initialize the settings manager and create the EasyStore
|
|
*/
|
|
public async init() {
|
|
this.settingsStore = await this.cloudlyRef.mongodbConnector.smartdataDb
|
|
.createEasyStore('cloudly-settings') as plugins.smartdata.EasyStore<servezoneInterfaces.data.ICloudlySettings>;
|
|
|
|
// Setup API route handlers
|
|
await this.setupRoutes();
|
|
|
|
this.readyDeferred.resolve();
|
|
}
|
|
|
|
/**
|
|
* Get all settings
|
|
*/
|
|
public async getSettings(): Promise<servezoneInterfaces.data.ICloudlySettings> {
|
|
await this.readyDeferred.promise;
|
|
return await this.settingsStore.readAll();
|
|
}
|
|
|
|
/**
|
|
* Get all settings with masked sensitive values (for API responses)
|
|
*/
|
|
public async getSettingsMasked(): Promise<servezoneInterfaces.data.ICloudlySettingsMasked> {
|
|
await this.readyDeferred.promise;
|
|
const settings = await this.getSettings();
|
|
const masked: servezoneInterfaces.data.ICloudlySettingsMasked = {};
|
|
|
|
for (const [key, value] of Object.entries(settings)) {
|
|
if (this.isSensitiveSettingKey(key) && typeof value === 'string' && value.length > 4) {
|
|
// Mask the token, showing only last 4 characters
|
|
masked[key] = '****' + value.slice(-4);
|
|
} else {
|
|
masked[key] = value;
|
|
}
|
|
}
|
|
|
|
return masked;
|
|
}
|
|
|
|
private isSensitiveSettingKey(key: string): boolean {
|
|
if (key === 'corebuildWorkersJson') {
|
|
return true;
|
|
}
|
|
const normalizedKey = key.toLowerCase();
|
|
return [
|
|
'token',
|
|
'secret',
|
|
'apikey',
|
|
'accesskey',
|
|
'applicationkey',
|
|
'consumerkey',
|
|
'keyjson',
|
|
'privatekey',
|
|
'password',
|
|
].some((sensitivePart) => normalizedKey.includes(sensitivePart));
|
|
}
|
|
|
|
/**
|
|
* Update multiple settings at once
|
|
*/
|
|
public async updateSettings(updates: Partial<servezoneInterfaces.data.ICloudlySettings>): Promise<void> {
|
|
await this.readyDeferred.promise;
|
|
for (const [key, value] of Object.entries(updates)) {
|
|
if (value !== undefined && value !== '') {
|
|
await this.settingsStore.writeKey(key as keyof servezoneInterfaces.data.ICloudlySettings, value);
|
|
} else if (value === '') {
|
|
// Empty string means clear the setting
|
|
await this.settingsStore.deleteKey(key as keyof servezoneInterfaces.data.ICloudlySettings);
|
|
}
|
|
}
|
|
|
|
if (Object.keys(updates).some((key) => this.isExternalGatewaySettingKey(key))) {
|
|
this.refreshExternalGatewayConfig().catch((error) => {
|
|
console.log(`External gateway settings refresh failed: ${(error as Error).message}`);
|
|
});
|
|
}
|
|
}
|
|
|
|
private isExternalGatewaySettingKey(key: string): boolean {
|
|
return [
|
|
'dcrouterGatewayUrl',
|
|
'dcrouterGatewayApiToken',
|
|
'dcrouterWorkHosterId',
|
|
'dcrouterTargetHost',
|
|
'dcrouterTargetPort',
|
|
].includes(key);
|
|
}
|
|
|
|
private async refreshExternalGatewayConfig(): Promise<void> {
|
|
await Promise.all([
|
|
this.cloudlyRef.domainManager.syncExternalGatewayDomains(),
|
|
this.cloudlyRef.coreflowManager.pushClusterConfigToConnectedCoreflows(),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Get a specific setting value
|
|
*/
|
|
public async getSetting<K extends keyof servezoneInterfaces.data.ICloudlySettings>(key: K): Promise<servezoneInterfaces.data.ICloudlySettings[K]> {
|
|
await this.readyDeferred.promise;
|
|
return await this.settingsStore.readKey(key);
|
|
}
|
|
|
|
/**
|
|
* Set a specific setting value
|
|
*/
|
|
public async setSetting<K extends keyof servezoneInterfaces.data.ICloudlySettings>(key: K, value: servezoneInterfaces.data.ICloudlySettings[K]): Promise<void> {
|
|
await this.readyDeferred.promise;
|
|
if (value !== undefined && value !== '') {
|
|
await this.settingsStore.writeKey(key, value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear a specific setting
|
|
*/
|
|
public async clearSetting(key: keyof servezoneInterfaces.data.ICloudlySettings): Promise<void> {
|
|
await this.readyDeferred.promise;
|
|
await this.settingsStore.deleteKey(key);
|
|
}
|
|
|
|
/**
|
|
* Clear all settings
|
|
*/
|
|
public async clearAllSettings(): Promise<void> {
|
|
await this.readyDeferred.promise;
|
|
await this.settingsStore.wipe();
|
|
}
|
|
|
|
/**
|
|
* Test connection for a specific provider
|
|
*/
|
|
public async testProviderConnection(provider: string): Promise<{success: boolean; message: string}> {
|
|
await this.readyDeferred.promise;
|
|
try {
|
|
switch (provider) {
|
|
case 'hetzner':
|
|
const hetznerToken = await this.getSetting('hetznerToken');
|
|
if (!hetznerToken) {
|
|
return { success: false, message: 'No Hetzner token configured' };
|
|
}
|
|
// TODO: Implement actual Hetzner API test
|
|
return { success: true, message: 'Hetzner connection test successful' };
|
|
|
|
case 'cloudflare':
|
|
const cloudflareToken = await this.getSetting('cloudflareToken');
|
|
if (!cloudflareToken) {
|
|
return { success: false, message: 'No Cloudflare token configured' };
|
|
}
|
|
// TODO: Implement actual Cloudflare API test
|
|
return { success: true, message: 'Cloudflare connection test successful' };
|
|
|
|
case 'aws':
|
|
const awsKey = await this.getSetting('awsAccessKey');
|
|
const awsSecret = await this.getSetting('awsSecretKey');
|
|
if (!awsKey || !awsSecret) {
|
|
return { success: false, message: 'AWS credentials not configured' };
|
|
}
|
|
// TODO: Implement actual AWS API test
|
|
return { success: true, message: 'AWS connection test successful' };
|
|
|
|
case 'digitalocean':
|
|
const doToken = await this.getSetting('digitalOceanToken');
|
|
if (!doToken) {
|
|
return { success: false, message: 'No DigitalOcean token configured' };
|
|
}
|
|
// TODO: Implement actual DigitalOcean API test
|
|
return { success: true, message: 'DigitalOcean connection test successful' };
|
|
|
|
case 'azure':
|
|
const azureClientId = await this.getSetting('azureClientId');
|
|
const azureClientSecret = await this.getSetting('azureClientSecret');
|
|
const azureTenantId = await this.getSetting('azureTenantId');
|
|
if (!azureClientId || !azureClientSecret || !azureTenantId) {
|
|
return { success: false, message: 'Azure credentials not configured' };
|
|
}
|
|
// TODO: Implement actual Azure API test
|
|
return { success: true, message: 'Azure connection test successful' };
|
|
|
|
default:
|
|
return { success: false, message: `Unknown provider: ${provider}` };
|
|
}
|
|
} catch (error) {
|
|
return { success: false, message: `Connection test failed: ${error instanceof Error ? error.message : String(error)}` };
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Setup API route handlers for settings management
|
|
*/
|
|
private async setupRoutes() {
|
|
// Get Settings Handler
|
|
this.cloudlyRef.typedrouter.addTypedHandler<servezoneInterfaces.requests.settings.IRequest_GetSettings>(
|
|
new plugins.typedrequest.TypedHandler<servezoneInterfaces.requests.settings.IRequest_GetSettings>(
|
|
'getSettings',
|
|
async (requestData) => {
|
|
// TODO: Add authentication check for admin users
|
|
const maskedSettings = await this.getSettingsMasked();
|
|
return {
|
|
settings: maskedSettings
|
|
};
|
|
}
|
|
)
|
|
);
|
|
|
|
// Update Settings Handler
|
|
this.cloudlyRef.typedrouter.addTypedHandler<servezoneInterfaces.requests.settings.IRequest_UpdateSettings>(
|
|
new plugins.typedrequest.TypedHandler<servezoneInterfaces.requests.settings.IRequest_UpdateSettings>(
|
|
'updateSettings',
|
|
async (requestData) => {
|
|
// TODO: Add authentication check for admin users
|
|
try {
|
|
await this.updateSettings(requestData.updates);
|
|
return {
|
|
success: true,
|
|
message: 'Settings updated successfully'
|
|
};
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
return {
|
|
success: false,
|
|
message: `Failed to update settings: ${errorMessage}`
|
|
};
|
|
}
|
|
}
|
|
)
|
|
);
|
|
|
|
// Clear Setting Handler
|
|
this.cloudlyRef.typedrouter.addTypedHandler<servezoneInterfaces.requests.settings.IRequest_ClearSetting>(
|
|
new plugins.typedrequest.TypedHandler<servezoneInterfaces.requests.settings.IRequest_ClearSetting>(
|
|
'clearSetting',
|
|
async (requestData) => {
|
|
// TODO: Add authentication check for admin users
|
|
try {
|
|
await this.clearSetting(requestData.key);
|
|
return {
|
|
success: true,
|
|
message: `Setting ${requestData.key} cleared successfully`
|
|
};
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
return {
|
|
success: false,
|
|
message: `Failed to clear setting: ${errorMessage}`
|
|
};
|
|
}
|
|
}
|
|
)
|
|
);
|
|
|
|
// Test Provider Connection Handler
|
|
this.cloudlyRef.typedrouter.addTypedHandler<servezoneInterfaces.requests.settings.IRequest_TestProviderConnection>(
|
|
new plugins.typedrequest.TypedHandler<servezoneInterfaces.requests.settings.IRequest_TestProviderConnection>(
|
|
'testProviderConnection',
|
|
async (requestData) => {
|
|
// TODO: Add authentication check for admin users
|
|
const testResult = await this.testProviderConnection(requestData.provider);
|
|
return {
|
|
success: testResult.success,
|
|
message: testResult.message,
|
|
connectionValid: testResult.success
|
|
};
|
|
}
|
|
)
|
|
);
|
|
|
|
// Get Single Setting Handler (for internal use)
|
|
this.cloudlyRef.typedrouter.addTypedHandler<servezoneInterfaces.requests.settings.IRequest_GetSetting>(
|
|
new plugins.typedrequest.TypedHandler<servezoneInterfaces.requests.settings.IRequest_GetSetting>(
|
|
'getSetting',
|
|
async (requestData) => {
|
|
// TODO: Add authentication check for admin users
|
|
const value = await this.getSetting(requestData.key);
|
|
return {
|
|
value
|
|
};
|
|
}
|
|
)
|
|
);
|
|
}
|
|
}
|