feat(cluster): add cluster configuration support across server, CLI, and admin UI
This commit is contained in:
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@lossless.zone/objectstorage',
|
||||
version: '1.6.0',
|
||||
version: '1.7.0',
|
||||
description: 'object storage server with management UI powered by smartstorage'
|
||||
}
|
||||
|
||||
@@ -39,6 +39,37 @@ export class ObjectStorageContainer {
|
||||
const envRegion = Deno.env.get('OBJST_REGION');
|
||||
if (envRegion) this.config.region = envRegion;
|
||||
|
||||
// Cluster environment variables
|
||||
const envClusterEnabled = Deno.env.get('OBJST_CLUSTER_ENABLED');
|
||||
if (envClusterEnabled) this.config.clusterEnabled = envClusterEnabled === 'true' || envClusterEnabled === '1';
|
||||
|
||||
const envClusterNodeId = Deno.env.get('OBJST_CLUSTER_NODE_ID');
|
||||
if (envClusterNodeId) this.config.clusterNodeId = envClusterNodeId;
|
||||
|
||||
const envClusterQuicPort = Deno.env.get('OBJST_CLUSTER_QUIC_PORT');
|
||||
if (envClusterQuicPort) this.config.clusterQuicPort = parseInt(envClusterQuicPort, 10);
|
||||
|
||||
const envClusterSeedNodes = Deno.env.get('OBJST_CLUSTER_SEED_NODES');
|
||||
if (envClusterSeedNodes) this.config.clusterSeedNodes = envClusterSeedNodes.split(',').map(s => s.trim()).filter(Boolean);
|
||||
|
||||
const envDrivePaths = Deno.env.get('OBJST_DRIVE_PATHS');
|
||||
if (envDrivePaths) this.config.drivePaths = envDrivePaths.split(',').map(s => s.trim()).filter(Boolean);
|
||||
|
||||
const envErasureDataShards = Deno.env.get('OBJST_ERASURE_DATA_SHARDS');
|
||||
if (envErasureDataShards) this.config.erasureDataShards = parseInt(envErasureDataShards, 10);
|
||||
|
||||
const envErasureParityShards = Deno.env.get('OBJST_ERASURE_PARITY_SHARDS');
|
||||
if (envErasureParityShards) this.config.erasureParityShards = parseInt(envErasureParityShards, 10);
|
||||
|
||||
const envErasureChunkSize = Deno.env.get('OBJST_ERASURE_CHUNK_SIZE');
|
||||
if (envErasureChunkSize) this.config.erasureChunkSizeBytes = parseInt(envErasureChunkSize, 10);
|
||||
|
||||
const envHeartbeatInterval = Deno.env.get('OBJST_HEARTBEAT_INTERVAL_MS');
|
||||
if (envHeartbeatInterval) this.config.clusterHeartbeatIntervalMs = parseInt(envHeartbeatInterval, 10);
|
||||
|
||||
const envHeartbeatTimeout = Deno.env.get('OBJST_HEARTBEAT_TIMEOUT_MS');
|
||||
if (envHeartbeatTimeout) this.config.clusterHeartbeatTimeoutMs = parseInt(envHeartbeatTimeout, 10);
|
||||
|
||||
this.opsServer = new OpsServer(this);
|
||||
this.policyManager = new PolicyManager(this);
|
||||
}
|
||||
@@ -49,9 +80,17 @@ export class ObjectStorageContainer {
|
||||
console.log(` UI port: ${this.config.uiPort}`);
|
||||
console.log(` Storage: ${this.config.storageDirectory}`);
|
||||
console.log(` Region: ${this.config.region}`);
|
||||
console.log(` Cluster: ${this.config.clusterEnabled ? 'enabled' : 'disabled'}`);
|
||||
if (this.config.clusterEnabled) {
|
||||
console.log(` Node ID: ${this.config.clusterNodeId || '(auto-generated)'}`);
|
||||
console.log(` QUIC Port: ${this.config.clusterQuicPort}`);
|
||||
console.log(` Seed Nodes: ${this.config.clusterSeedNodes.join(', ') || '(none)'}`);
|
||||
console.log(` Drives: ${this.config.drivePaths.length > 0 ? this.config.drivePaths.join(', ') : this.config.storageDirectory}`);
|
||||
console.log(` Erasure: ${this.config.erasureDataShards}+${this.config.erasureParityShards}`);
|
||||
}
|
||||
|
||||
// Start smartstorage
|
||||
this.smartstorageInstance = await plugins.smartstorage.SmartStorage.createAndStart({
|
||||
// Build smartstorage config
|
||||
const smartstorageConfig: any = {
|
||||
server: {
|
||||
port: this.config.objstPort,
|
||||
address: '0.0.0.0',
|
||||
@@ -64,7 +103,31 @@ export class ObjectStorageContainer {
|
||||
enabled: true,
|
||||
credentials: this.config.accessCredentials,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
if (this.config.clusterEnabled) {
|
||||
smartstorageConfig.cluster = {
|
||||
enabled: true,
|
||||
nodeId: this.config.clusterNodeId || crypto.randomUUID().slice(0, 8),
|
||||
quicPort: this.config.clusterQuicPort,
|
||||
seedNodes: this.config.clusterSeedNodes,
|
||||
erasure: {
|
||||
dataShards: this.config.erasureDataShards,
|
||||
parityShards: this.config.erasureParityShards,
|
||||
chunkSizeBytes: this.config.erasureChunkSizeBytes,
|
||||
},
|
||||
drives: {
|
||||
paths: this.config.drivePaths.length > 0
|
||||
? this.config.drivePaths
|
||||
: [this.config.storageDirectory],
|
||||
},
|
||||
heartbeatIntervalMs: this.config.clusterHeartbeatIntervalMs,
|
||||
heartbeatTimeoutMs: this.config.clusterHeartbeatTimeoutMs,
|
||||
};
|
||||
}
|
||||
|
||||
// Start smartstorage
|
||||
this.smartstorageInstance = await plugins.smartstorage.SmartStorage.createAndStart(smartstorageConfig);
|
||||
|
||||
this.startedAt = Date.now();
|
||||
console.log(`Storage server started on port ${this.config.objstPort}`);
|
||||
|
||||
64
ts/cli.ts
64
ts/cli.ts
@@ -29,6 +29,27 @@ export async function runCli(): Promise<void> {
|
||||
// Use a temp directory for storage
|
||||
configOverrides.storageDirectory = './.nogit/objstdata';
|
||||
break;
|
||||
case '--cluster-enabled':
|
||||
configOverrides.clusterEnabled = true;
|
||||
break;
|
||||
case '--cluster-node-id':
|
||||
configOverrides.clusterNodeId = args[++i];
|
||||
break;
|
||||
case '--cluster-quic-port':
|
||||
configOverrides.clusterQuicPort = parseInt(args[++i], 10);
|
||||
break;
|
||||
case '--cluster-seed-nodes':
|
||||
configOverrides.clusterSeedNodes = args[++i].split(',').map(s => s.trim()).filter(Boolean);
|
||||
break;
|
||||
case '--drive-paths':
|
||||
configOverrides.drivePaths = args[++i].split(',').map(s => s.trim()).filter(Boolean);
|
||||
break;
|
||||
case '--erasure-data-shards':
|
||||
configOverrides.erasureDataShards = parseInt(args[++i], 10);
|
||||
break;
|
||||
case '--erasure-parity-shards':
|
||||
configOverrides.erasureParityShards = parseInt(args[++i], 10);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,19 +80,38 @@ ObjectStorage - S3-compatible object storage server with management UI
|
||||
Usage:
|
||||
objectstorage server [options]
|
||||
|
||||
Options:
|
||||
--ephemeral Use local .nogit/objstdata for storage
|
||||
--storage-port PORT Storage API port (default: 9000, env: OBJST_PORT)
|
||||
--ui-port PORT Management UI port (default: 3000, env: UI_PORT)
|
||||
--storage-dir DIR Storage directory (default: /data, env: OBJST_STORAGE_DIR)
|
||||
Server Options:
|
||||
--ephemeral Use local .nogit/objstdata for storage
|
||||
--storage-port PORT Storage API port (default: 9000, env: OBJST_PORT)
|
||||
--ui-port PORT Management UI port (default: 3000, env: UI_PORT)
|
||||
--storage-dir DIR Storage directory (default: /data, env: OBJST_STORAGE_DIR)
|
||||
|
||||
Clustering Options:
|
||||
--cluster-enabled Enable cluster mode (env: OBJST_CLUSTER_ENABLED)
|
||||
--cluster-node-id ID Unique node identifier (env: OBJST_CLUSTER_NODE_ID)
|
||||
--cluster-quic-port PORT QUIC transport port (default: 4433, env: OBJST_CLUSTER_QUIC_PORT)
|
||||
--cluster-seed-nodes LIST Comma-separated seed node addresses (env: OBJST_CLUSTER_SEED_NODES)
|
||||
--drive-paths LIST Comma-separated drive mount paths (env: OBJST_DRIVE_PATHS)
|
||||
--erasure-data-shards N Erasure coding data shards (default: 4, env: OBJST_ERASURE_DATA_SHARDS)
|
||||
--erasure-parity-shards N Erasure coding parity shards (default: 2, env: OBJST_ERASURE_PARITY_SHARDS)
|
||||
|
||||
Environment Variables:
|
||||
OBJST_PORT Storage API port
|
||||
UI_PORT Management UI port
|
||||
OBJST_STORAGE_DIR Storage directory
|
||||
OBJST_ACCESS_KEY Access key (default: admin)
|
||||
OBJST_SECRET_KEY Secret key (default: admin)
|
||||
OBJST_ADMIN_PASSWORD Admin UI password (default: admin)
|
||||
OBJST_REGION Storage region (default: us-east-1)
|
||||
OBJST_PORT Storage API port
|
||||
UI_PORT Management UI port
|
||||
OBJST_STORAGE_DIR Storage directory
|
||||
OBJST_ACCESS_KEY Access key (default: admin)
|
||||
OBJST_SECRET_KEY Secret key (default: admin)
|
||||
OBJST_ADMIN_PASSWORD Admin UI password (default: admin)
|
||||
OBJST_REGION Storage region (default: us-east-1)
|
||||
OBJST_CLUSTER_ENABLED Enable clustering (true/false)
|
||||
OBJST_CLUSTER_NODE_ID Unique node identifier
|
||||
OBJST_CLUSTER_QUIC_PORT QUIC transport port (default: 4433)
|
||||
OBJST_CLUSTER_SEED_NODES Comma-separated seed node addresses
|
||||
OBJST_DRIVE_PATHS Comma-separated drive mount paths
|
||||
OBJST_ERASURE_DATA_SHARDS Erasure data shards (default: 4)
|
||||
OBJST_ERASURE_PARITY_SHARDS Erasure parity shards (default: 2)
|
||||
OBJST_ERASURE_CHUNK_SIZE Erasure chunk size in bytes (default: 4194304)
|
||||
OBJST_HEARTBEAT_INTERVAL_MS Cluster heartbeat interval (default: 5000)
|
||||
OBJST_HEARTBEAT_TIMEOUT_MS Cluster heartbeat timeout (default: 30000)
|
||||
`);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,16 @@ export class ConfigHandler {
|
||||
storageDirectory: containerConfig.storageDirectory,
|
||||
authEnabled: true,
|
||||
corsEnabled: false,
|
||||
clusterEnabled: containerConfig.clusterEnabled,
|
||||
clusterNodeId: containerConfig.clusterNodeId,
|
||||
clusterQuicPort: containerConfig.clusterQuicPort,
|
||||
clusterSeedNodes: containerConfig.clusterSeedNodes,
|
||||
erasureDataShards: containerConfig.erasureDataShards,
|
||||
erasureParityShards: containerConfig.erasureParityShards,
|
||||
erasureChunkSizeBytes: containerConfig.erasureChunkSizeBytes,
|
||||
drivePaths: containerConfig.drivePaths,
|
||||
clusterHeartbeatIntervalMs: containerConfig.clusterHeartbeatIntervalMs,
|
||||
clusterHeartbeatTimeoutMs: containerConfig.clusterHeartbeatTimeoutMs,
|
||||
};
|
||||
return { config };
|
||||
},
|
||||
|
||||
24
ts/types.ts
24
ts/types.ts
@@ -5,6 +5,20 @@ export interface IObjectStorageConfig {
|
||||
accessCredentials: Array<{ accessKeyId: string; secretAccessKey: string }>;
|
||||
adminPassword: string;
|
||||
region: string;
|
||||
// Cluster
|
||||
clusterEnabled: boolean;
|
||||
clusterNodeId: string;
|
||||
clusterQuicPort: number;
|
||||
clusterSeedNodes: string[];
|
||||
// Erasure coding
|
||||
erasureDataShards: number;
|
||||
erasureParityShards: number;
|
||||
erasureChunkSizeBytes: number;
|
||||
// Multi-drive
|
||||
drivePaths: string[];
|
||||
// Cluster heartbeat
|
||||
clusterHeartbeatIntervalMs: number;
|
||||
clusterHeartbeatTimeoutMs: number;
|
||||
}
|
||||
|
||||
export const defaultConfig: IObjectStorageConfig = {
|
||||
@@ -14,4 +28,14 @@ export const defaultConfig: IObjectStorageConfig = {
|
||||
accessCredentials: [{ accessKeyId: 'admin', secretAccessKey: 'admin' }],
|
||||
adminPassword: 'admin',
|
||||
region: 'us-east-1',
|
||||
clusterEnabled: false,
|
||||
clusterNodeId: '',
|
||||
clusterQuicPort: 4433,
|
||||
clusterSeedNodes: [],
|
||||
erasureDataShards: 4,
|
||||
erasureParityShards: 2,
|
||||
erasureChunkSizeBytes: 4194304,
|
||||
drivePaths: [],
|
||||
clusterHeartbeatIntervalMs: 5000,
|
||||
clusterHeartbeatTimeoutMs: 30000,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user