250 lines
5.6 KiB
TypeScript
250 lines
5.6 KiB
TypeScript
import * as plugins from './plugins.js';
|
|
import * as paths from './paths.js';
|
|
|
|
/**
|
|
* Authentication configuration
|
|
*/
|
|
export interface IAuthConfig {
|
|
enabled: boolean;
|
|
credentials: Array<{
|
|
accessKeyId: string;
|
|
secretAccessKey: string;
|
|
}>;
|
|
}
|
|
|
|
/**
|
|
* CORS configuration
|
|
*/
|
|
export interface ICorsConfig {
|
|
enabled: boolean;
|
|
allowedOrigins?: string[];
|
|
allowedMethods?: string[];
|
|
allowedHeaders?: string[];
|
|
exposedHeaders?: string[];
|
|
maxAge?: number;
|
|
allowCredentials?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Logging configuration
|
|
*/
|
|
export interface ILoggingConfig {
|
|
level?: 'error' | 'warn' | 'info' | 'debug';
|
|
format?: 'text' | 'json';
|
|
enabled?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Request limits configuration
|
|
*/
|
|
export interface ILimitsConfig {
|
|
maxObjectSize?: number;
|
|
maxMetadataSize?: number;
|
|
requestTimeout?: number;
|
|
}
|
|
|
|
/**
|
|
* Multipart upload configuration
|
|
*/
|
|
export interface IMultipartConfig {
|
|
expirationDays?: number;
|
|
cleanupIntervalMinutes?: number;
|
|
}
|
|
|
|
/**
|
|
* Server configuration
|
|
*/
|
|
export interface IServerConfig {
|
|
port?: number;
|
|
address?: string;
|
|
silent?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Storage configuration
|
|
*/
|
|
export interface IStorageConfig {
|
|
directory?: string;
|
|
cleanSlate?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Complete smarts3 configuration
|
|
*/
|
|
export interface ISmarts3Config {
|
|
server?: IServerConfig;
|
|
storage?: IStorageConfig;
|
|
auth?: IAuthConfig;
|
|
cors?: ICorsConfig;
|
|
logging?: ILoggingConfig;
|
|
limits?: ILimitsConfig;
|
|
multipart?: IMultipartConfig;
|
|
}
|
|
|
|
/**
|
|
* Default configuration values
|
|
*/
|
|
const DEFAULT_CONFIG: ISmarts3Config = {
|
|
server: {
|
|
port: 3000,
|
|
address: '0.0.0.0',
|
|
silent: false,
|
|
},
|
|
storage: {
|
|
directory: paths.bucketsDir,
|
|
cleanSlate: false,
|
|
},
|
|
auth: {
|
|
enabled: false,
|
|
credentials: [
|
|
{
|
|
accessKeyId: 'S3RVER',
|
|
secretAccessKey: 'S3RVER',
|
|
},
|
|
],
|
|
},
|
|
cors: {
|
|
enabled: false,
|
|
allowedOrigins: ['*'],
|
|
allowedMethods: ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'],
|
|
allowedHeaders: ['*'],
|
|
exposedHeaders: ['ETag', 'x-amz-request-id', 'x-amz-version-id'],
|
|
maxAge: 86400,
|
|
allowCredentials: false,
|
|
},
|
|
logging: {
|
|
level: 'info',
|
|
format: 'text',
|
|
enabled: true,
|
|
},
|
|
limits: {
|
|
maxObjectSize: 5 * 1024 * 1024 * 1024, // 5GB
|
|
maxMetadataSize: 2048,
|
|
requestTimeout: 300000, // 5 minutes
|
|
},
|
|
multipart: {
|
|
expirationDays: 7,
|
|
cleanupIntervalMinutes: 60,
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Merge user config with defaults (deep merge)
|
|
*/
|
|
function mergeConfig(userConfig: ISmarts3Config): Required<ISmarts3Config> {
|
|
return {
|
|
server: {
|
|
...DEFAULT_CONFIG.server!,
|
|
...(userConfig.server || {}),
|
|
},
|
|
storage: {
|
|
...DEFAULT_CONFIG.storage!,
|
|
...(userConfig.storage || {}),
|
|
},
|
|
auth: {
|
|
...DEFAULT_CONFIG.auth!,
|
|
...(userConfig.auth || {}),
|
|
},
|
|
cors: {
|
|
...DEFAULT_CONFIG.cors!,
|
|
...(userConfig.cors || {}),
|
|
},
|
|
logging: {
|
|
...DEFAULT_CONFIG.logging!,
|
|
...(userConfig.logging || {}),
|
|
},
|
|
limits: {
|
|
...DEFAULT_CONFIG.limits!,
|
|
...(userConfig.limits || {}),
|
|
},
|
|
multipart: {
|
|
...DEFAULT_CONFIG.multipart!,
|
|
...(userConfig.multipart || {}),
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* IPC command type map for RustBridge
|
|
*/
|
|
type TRustS3Commands = {
|
|
start: { params: { config: Required<ISmarts3Config> }; result: {} };
|
|
stop: { params: {}; result: {} };
|
|
createBucket: { params: { name: string }; result: {} };
|
|
};
|
|
|
|
/**
|
|
* Main Smarts3 class - production-ready S3-compatible server
|
|
*/
|
|
export class Smarts3 {
|
|
// STATIC
|
|
public static async createAndStart(configArg: ISmarts3Config = {}) {
|
|
const smartS3Instance = new Smarts3(configArg);
|
|
await smartS3Instance.start();
|
|
return smartS3Instance;
|
|
}
|
|
|
|
// INSTANCE
|
|
public config: Required<ISmarts3Config>;
|
|
private bridge: InstanceType<typeof plugins.RustBridge<TRustS3Commands>>;
|
|
|
|
constructor(configArg: ISmarts3Config = {}) {
|
|
this.config = mergeConfig(configArg);
|
|
this.bridge = new plugins.RustBridge<TRustS3Commands>({
|
|
binaryName: 'rusts3',
|
|
localPaths: [
|
|
plugins.path.join(paths.packageDir, 'dist_rust', 'rusts3'),
|
|
plugins.path.join(paths.packageDir, 'rust', 'target', 'release', 'rusts3'),
|
|
plugins.path.join(paths.packageDir, 'rust', 'target', 'debug', 'rusts3'),
|
|
],
|
|
readyTimeoutMs: 30000,
|
|
requestTimeoutMs: 300000,
|
|
});
|
|
}
|
|
|
|
public async start() {
|
|
const spawned = await this.bridge.spawn();
|
|
if (!spawned) {
|
|
throw new Error('Failed to spawn rusts3 binary. Make sure it is compiled (pnpm build).');
|
|
}
|
|
await this.bridge.sendCommand('start', { config: this.config });
|
|
|
|
if (!this.config.server.silent) {
|
|
console.log('s3 server is running');
|
|
}
|
|
}
|
|
|
|
public async getS3Descriptor(
|
|
optionsArg?: Partial<plugins.tsclass.storage.IS3Descriptor>,
|
|
): Promise<plugins.tsclass.storage.IS3Descriptor> {
|
|
const cred = this.config.auth.credentials[0] || {
|
|
accessKeyId: 'S3RVER',
|
|
secretAccessKey: 'S3RVER',
|
|
};
|
|
|
|
const descriptor: plugins.tsclass.storage.IS3Descriptor = {
|
|
endpoint: this.config.server.address === '0.0.0.0' ? 'localhost' : this.config.server.address!,
|
|
port: this.config.server.port!,
|
|
useSsl: false,
|
|
accessKey: cred.accessKeyId,
|
|
accessSecret: cred.secretAccessKey,
|
|
bucketName: '',
|
|
};
|
|
|
|
return {
|
|
...descriptor,
|
|
...(optionsArg ? optionsArg : {}),
|
|
};
|
|
}
|
|
|
|
public async createBucket(bucketNameArg: string) {
|
|
await this.bridge.sendCommand('createBucket', { name: bucketNameArg });
|
|
return { name: bucketNameArg };
|
|
}
|
|
|
|
public async stop() {
|
|
await this.bridge.sendCommand('stop', {});
|
|
this.bridge.kill();
|
|
}
|
|
}
|