2025-10-28 13:05:42 +00:00
|
|
|
/**
|
|
|
|
|
* Registry Manager for Onebox
|
|
|
|
|
*
|
|
|
|
|
* Manages Docker registry credentials and authentication
|
|
|
|
|
*/
|
|
|
|
|
|
2025-11-18 00:03:24 +00:00
|
|
|
import * as plugins from '../plugins.ts';
|
|
|
|
|
import type { IRegistry } from '../types.ts';
|
|
|
|
|
import { logger } from '../logging.ts';
|
2025-11-25 04:38:26 +00:00
|
|
|
import { getErrorMessage } from '../utils/error.ts';
|
2025-11-18 00:03:24 +00:00
|
|
|
import { OneboxDatabase } from './database.ts';
|
2025-10-28 13:05:42 +00:00
|
|
|
|
|
|
|
|
export class OneboxRegistriesManager {
|
|
|
|
|
private oneboxRef: any; // Will be Onebox instance
|
|
|
|
|
private database: OneboxDatabase;
|
|
|
|
|
|
|
|
|
|
constructor(oneboxRef: any) {
|
|
|
|
|
this.oneboxRef = oneboxRef;
|
|
|
|
|
this.database = oneboxRef.database;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Encrypt a password (simple base64 for now, should use proper encryption)
|
|
|
|
|
*/
|
|
|
|
|
private encryptPassword(password: string): string {
|
|
|
|
|
// TODO: Use proper encryption with a secret key
|
|
|
|
|
// For now, using base64 encoding (NOT SECURE, just for structure)
|
|
|
|
|
return plugins.encoding.encodeBase64(new TextEncoder().encode(password));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Decrypt a password
|
|
|
|
|
*/
|
|
|
|
|
private decryptPassword(encrypted: string): string {
|
|
|
|
|
// TODO: Use proper decryption
|
|
|
|
|
return new TextDecoder().decode(plugins.encoding.decodeBase64(encrypted));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add a registry
|
|
|
|
|
*/
|
|
|
|
|
async addRegistry(url: string, username: string, password: string): Promise<IRegistry> {
|
|
|
|
|
try {
|
|
|
|
|
// Check if registry already exists
|
|
|
|
|
const existing = this.database.getRegistryByURL(url);
|
|
|
|
|
if (existing) {
|
|
|
|
|
throw new Error(`Registry already exists: ${url}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Encrypt password
|
|
|
|
|
const passwordEncrypted = this.encryptPassword(password);
|
|
|
|
|
|
|
|
|
|
// Create registry in database
|
|
|
|
|
const registry = await this.database.createRegistry({
|
|
|
|
|
url,
|
|
|
|
|
username,
|
|
|
|
|
passwordEncrypted,
|
|
|
|
|
createdAt: Date.now(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
logger.success(`Registry added: ${url}`);
|
|
|
|
|
|
|
|
|
|
// Perform Docker login
|
|
|
|
|
await this.loginToRegistry(registry);
|
|
|
|
|
|
|
|
|
|
return registry;
|
|
|
|
|
} catch (error) {
|
2025-11-25 04:38:26 +00:00
|
|
|
logger.error(`Failed to add registry ${url}: ${getErrorMessage(error)}`);
|
2025-10-28 13:05:42 +00:00
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Remove a registry
|
|
|
|
|
*/
|
|
|
|
|
async removeRegistry(url: string): Promise<void> {
|
|
|
|
|
try {
|
|
|
|
|
const registry = this.database.getRegistryByURL(url);
|
|
|
|
|
if (!registry) {
|
|
|
|
|
throw new Error(`Registry not found: ${url}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.database.deleteRegistry(url);
|
|
|
|
|
logger.success(`Registry removed: ${url}`);
|
|
|
|
|
|
|
|
|
|
// Note: We don't perform docker logout as it might affect other users
|
|
|
|
|
} catch (error) {
|
2025-11-25 04:38:26 +00:00
|
|
|
logger.error(`Failed to remove registry ${url}: ${getErrorMessage(error)}`);
|
2025-10-28 13:05:42 +00:00
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* List all registries
|
|
|
|
|
*/
|
|
|
|
|
listRegistries(): IRegistry[] {
|
|
|
|
|
return this.database.getAllRegistries();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get registry by URL
|
|
|
|
|
*/
|
|
|
|
|
getRegistry(url: string): IRegistry | null {
|
|
|
|
|
return this.database.getRegistryByURL(url);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Perform Docker login for a registry
|
|
|
|
|
*/
|
|
|
|
|
async loginToRegistry(registry: IRegistry): Promise<void> {
|
|
|
|
|
try {
|
|
|
|
|
logger.info(`Logging into registry: ${registry.url}`);
|
|
|
|
|
|
|
|
|
|
const password = this.decryptPassword(registry.passwordEncrypted);
|
|
|
|
|
|
|
|
|
|
// Use docker login command
|
|
|
|
|
const command = [
|
|
|
|
|
'docker',
|
|
|
|
|
'login',
|
|
|
|
|
registry.url,
|
|
|
|
|
'--username',
|
|
|
|
|
registry.username,
|
|
|
|
|
'--password-stdin',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const process = new Deno.Command('docker', {
|
|
|
|
|
args: ['login', registry.url, '--username', registry.username, '--password-stdin'],
|
|
|
|
|
stdin: 'piped',
|
|
|
|
|
stdout: 'piped',
|
|
|
|
|
stderr: 'piped',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const child = process.spawn();
|
|
|
|
|
|
|
|
|
|
// Write password to stdin
|
|
|
|
|
const writer = child.stdin.getWriter();
|
|
|
|
|
await writer.write(new TextEncoder().encode(password));
|
|
|
|
|
await writer.close();
|
|
|
|
|
|
|
|
|
|
const { code, stdout, stderr } = await child.output();
|
|
|
|
|
|
|
|
|
|
if (code !== 0) {
|
|
|
|
|
const errorMsg = new TextDecoder().decode(stderr);
|
|
|
|
|
throw new Error(`Docker login failed: ${errorMsg}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.success(`Logged into registry: ${registry.url}`);
|
|
|
|
|
} catch (error) {
|
2025-11-25 04:38:26 +00:00
|
|
|
logger.error(`Failed to login to registry ${registry.url}: ${getErrorMessage(error)}`);
|
2025-10-28 13:05:42 +00:00
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Login to all registries (useful on daemon start)
|
|
|
|
|
*/
|
|
|
|
|
async loginToAllRegistries(): Promise<void> {
|
|
|
|
|
const registries = this.listRegistries();
|
|
|
|
|
|
|
|
|
|
for (const registry of registries) {
|
|
|
|
|
try {
|
|
|
|
|
await this.loginToRegistry(registry);
|
|
|
|
|
} catch (error) {
|
2025-11-25 04:38:26 +00:00
|
|
|
logger.warn(`Failed to login to ${registry.url}: ${getErrorMessage(error)}`);
|
2025-10-28 13:05:42 +00:00
|
|
|
// Continue with other registries
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Test registry connection
|
|
|
|
|
*/
|
|
|
|
|
async testRegistry(url: string, username: string, password: string): Promise<boolean> {
|
|
|
|
|
try {
|
|
|
|
|
const command = new Deno.Command('docker', {
|
|
|
|
|
args: ['login', url, '--username', username, '--password-stdin'],
|
|
|
|
|
stdin: 'piped',
|
|
|
|
|
stdout: 'piped',
|
|
|
|
|
stderr: 'piped',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const child = command.spawn();
|
|
|
|
|
|
|
|
|
|
const writer = child.stdin.getWriter();
|
|
|
|
|
await writer.write(new TextEncoder().encode(password));
|
|
|
|
|
await writer.close();
|
|
|
|
|
|
|
|
|
|
const { code } = await child.output();
|
|
|
|
|
|
|
|
|
|
return code === 0;
|
|
|
|
|
} catch (error) {
|
2025-11-25 04:38:26 +00:00
|
|
|
logger.error(`Failed to test registry ${url}: ${getErrorMessage(error)}`);
|
2025-10-28 13:05:42 +00:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|