Files
registry/ts/registry.ts

277 lines
7.9 KiB
TypeScript
Raw Normal View History

/**
* StackGalleryRegistry - Main registry class
* Integrates smartregistry with Stack.Gallery's auth, storage, and database
*/
import * as plugins from './plugins.ts';
import { initDb, closeDb, isDbConnected } from './models/db.ts';
import { StackGalleryAuthProvider } from './providers/auth.provider.ts';
import { StackGalleryStorageHooks } from './providers/storage.provider.ts';
import { ApiRouter } from './api/router.ts';
export interface IRegistryConfig {
// MongoDB configuration
mongoUrl: string;
mongoDb: string;
// S3 configuration
s3Endpoint: string;
s3AccessKey: string;
s3SecretKey: string;
s3Bucket: string;
s3Region?: string;
// Server configuration
host?: string;
port?: number;
// Registry settings
storagePath?: string;
enableUpstreamCache?: boolean;
upstreamCacheExpiry?: number; // hours
// JWT configuration
jwtSecret?: string;
}
export class StackGalleryRegistry {
private config: IRegistryConfig;
private smartBucket: plugins.smartbucket.SmartBucket | null = null;
private smartRegistry: plugins.smartregistry.SmartRegistry | null = null;
private authProvider: StackGalleryAuthProvider | null = null;
private storageHooks: StackGalleryStorageHooks | null = null;
private apiRouter: ApiRouter | null = null;
private isInitialized = false;
constructor(config: IRegistryConfig) {
this.config = {
host: '0.0.0.0',
port: 3000,
storagePath: 'packages',
enableUpstreamCache: true,
upstreamCacheExpiry: 24,
...config,
};
}
/**
* Initialize the registry
*/
public async init(): Promise<void> {
if (this.isInitialized) return;
console.log('[StackGalleryRegistry] Initializing...');
// Initialize MongoDB
console.log('[StackGalleryRegistry] Connecting to MongoDB...');
await initDb(this.config.mongoUrl, this.config.mongoDb);
console.log('[StackGalleryRegistry] MongoDB connected');
// Initialize S3/SmartBucket
console.log('[StackGalleryRegistry] Initializing S3 storage...');
this.smartBucket = new plugins.smartbucket.SmartBucket({
accessKey: this.config.s3AccessKey,
accessSecret: this.config.s3SecretKey,
endpoint: this.config.s3Endpoint,
bucketName: this.config.s3Bucket,
});
console.log('[StackGalleryRegistry] S3 storage initialized');
// Initialize auth provider
this.authProvider = new StackGalleryAuthProvider();
// Initialize storage hooks
this.storageHooks = new StackGalleryStorageHooks({
bucket: this.smartBucket,
basePath: this.config.storagePath!,
});
// Initialize smartregistry
console.log('[StackGalleryRegistry] Initializing smartregistry...');
this.smartRegistry = new plugins.smartregistry.SmartRegistry({
authProvider: this.authProvider,
storageHooks: this.storageHooks,
storage: {
type: 's3',
bucket: this.smartBucket,
basePath: this.config.storagePath,
},
upstreamCache: this.config.enableUpstreamCache
? {
enabled: true,
expiryHours: this.config.upstreamCacheExpiry,
}
: undefined,
});
console.log('[StackGalleryRegistry] smartregistry initialized');
// Initialize API router
console.log('[StackGalleryRegistry] Initializing API router...');
this.apiRouter = new ApiRouter();
console.log('[StackGalleryRegistry] API router initialized');
this.isInitialized = true;
console.log('[StackGalleryRegistry] Initialization complete');
}
/**
* Start the HTTP server
*/
public async start(): Promise<void> {
if (!this.isInitialized) {
await this.init();
}
const port = this.config.port!;
const host = this.config.host!;
console.log(`[StackGalleryRegistry] Starting server on ${host}:${port}...`);
Deno.serve(
{ port, hostname: host },
async (request: Request): Promise<Response> => {
return await this.handleRequest(request);
}
);
console.log(`[StackGalleryRegistry] Server running on http://${host}:${port}`);
}
/**
* Handle incoming HTTP request
*/
private async handleRequest(request: Request): Promise<Response> {
const url = new URL(request.url);
const path = url.pathname;
// Health check
if (path === '/health' || path === '/healthz') {
return this.healthCheck();
}
// API endpoints (handled by REST API layer)
if (path.startsWith('/api/')) {
return await this.handleApiRequest(request);
}
// Registry protocol endpoints
// NPM: /-/..., /@scope/package, /package
// OCI: /v2/...
// Maven: /maven2/...
// PyPI: /simple/..., /pypi/...
// Cargo: /api/v1/crates/...
// Composer: /packages.json, /p/...
// RubyGems: /api/v1/gems/..., /gems/...
if (this.smartRegistry) {
try {
return await this.smartRegistry.handleRequest(request);
} catch (error) {
console.error('[StackGalleryRegistry] Request error:', error);
return new Response(
JSON.stringify({ error: 'Internal server error' }),
{
status: 500,
headers: { 'Content-Type': 'application/json' },
}
);
}
}
return new Response('Not Found', { status: 404 });
}
/**
* Handle API requests
*/
private async handleApiRequest(request: Request): Promise<Response> {
if (!this.apiRouter) {
return new Response(
JSON.stringify({ error: 'API router not initialized' }),
{
status: 503,
headers: { 'Content-Type': 'application/json' },
}
);
}
return await this.apiRouter.handle(request);
}
/**
* Health check endpoint
*/
private healthCheck(): Response {
const healthy = this.isInitialized && isDbConnected();
const status = {
status: healthy ? 'healthy' : 'unhealthy',
timestamp: new Date().toISOString(),
services: {
mongodb: isDbConnected() ? 'connected' : 'disconnected',
s3: this.smartBucket ? 'initialized' : 'not initialized',
registry: this.smartRegistry ? 'initialized' : 'not initialized',
},
};
return new Response(JSON.stringify(status), {
status: healthy ? 200 : 503,
headers: { 'Content-Type': 'application/json' },
});
}
/**
* Stop the registry
*/
public async stop(): Promise<void> {
console.log('[StackGalleryRegistry] Shutting down...');
await closeDb();
this.isInitialized = false;
console.log('[StackGalleryRegistry] Shutdown complete');
}
/**
* Get the smartregistry instance
*/
public getSmartRegistry(): plugins.smartregistry.SmartRegistry | null {
return this.smartRegistry;
}
/**
* Get the smartbucket instance
*/
public getSmartBucket(): plugins.smartbucket.SmartBucket | null {
return this.smartBucket;
}
/**
* Check if registry is initialized
*/
public getIsInitialized(): boolean {
return this.isInitialized;
}
}
/**
* Create registry from environment variables
*/
export function createRegistryFromEnv(): StackGalleryRegistry {
const config: IRegistryConfig = {
mongoUrl: Deno.env.get('MONGODB_URL') || 'mongodb://localhost:27017',
mongoDb: Deno.env.get('MONGODB_DB') || 'stackgallery',
s3Endpoint: Deno.env.get('S3_ENDPOINT') || 'http://localhost:9000',
s3AccessKey: Deno.env.get('S3_ACCESS_KEY') || 'minioadmin',
s3SecretKey: Deno.env.get('S3_SECRET_KEY') || 'minioadmin',
s3Bucket: Deno.env.get('S3_BUCKET') || 'registry',
s3Region: Deno.env.get('S3_REGION'),
host: Deno.env.get('HOST') || '0.0.0.0',
port: parseInt(Deno.env.get('PORT') || '3000', 10),
storagePath: Deno.env.get('STORAGE_PATH') || 'packages',
enableUpstreamCache: Deno.env.get('ENABLE_UPSTREAM_CACHE') !== 'false',
upstreamCacheExpiry: parseInt(Deno.env.get('UPSTREAM_CACHE_EXPIRY') || '24', 10),
jwtSecret: Deno.env.get('JWT_SECRET'),
};
return new StackGalleryRegistry(config);
}