210 lines
5.5 KiB
TypeScript
210 lines
5.5 KiB
TypeScript
/**
|
|
* API Client for communicating with Onebox daemon
|
|
*
|
|
* Provides methods for CLI commands to interact with running daemon via HTTP API
|
|
*/
|
|
|
|
import type {
|
|
IService,
|
|
IRegistry,
|
|
IDnsRecord,
|
|
ISslCertificate,
|
|
IServiceDeployOptions,
|
|
} from '../types.ts';
|
|
|
|
export class OneboxApiClient {
|
|
private baseUrl: string;
|
|
private token?: string;
|
|
|
|
constructor(port = 3000) {
|
|
this.baseUrl = `http://localhost:${port}`;
|
|
}
|
|
|
|
/**
|
|
* Check if daemon is reachable
|
|
*/
|
|
async isReachable(): Promise<boolean> {
|
|
try {
|
|
const response = await fetch(`${this.baseUrl}/api/status`, {
|
|
signal: AbortSignal.timeout(5000), // 5 second timeout
|
|
});
|
|
return response.ok;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// ============ Service Operations ============
|
|
|
|
async deployService(config: IServiceDeployOptions): Promise<IService> {
|
|
return await this.request<IService>('POST', '/api/services', config);
|
|
}
|
|
|
|
async removeService(name: string): Promise<void> {
|
|
await this.request('DELETE', `/api/services/${name}`);
|
|
}
|
|
|
|
async startService(name: string): Promise<void> {
|
|
await this.request('POST', `/api/services/${name}/start`);
|
|
}
|
|
|
|
async stopService(name: string): Promise<void> {
|
|
await this.request('POST', `/api/services/${name}/stop`);
|
|
}
|
|
|
|
async restartService(name: string): Promise<void> {
|
|
await this.request('POST', `/api/services/${name}/restart`);
|
|
}
|
|
|
|
async listServices(): Promise<IService[]> {
|
|
return await this.request<IService[]>('GET', '/api/services');
|
|
}
|
|
|
|
async getServiceLogs(name: string, limit = 1000): Promise<string[]> {
|
|
const result = await this.request<{ logs: string[] }>(
|
|
'GET',
|
|
`/api/services/${name}/logs?limit=${limit}`
|
|
);
|
|
return result.logs;
|
|
}
|
|
|
|
// ============ Registry Operations ============
|
|
|
|
async addRegistry(url: string, username: string, password: string): Promise<void> {
|
|
await this.request('POST', '/api/registries', { url, username, password });
|
|
}
|
|
|
|
async removeRegistry(url: string): Promise<void> {
|
|
await this.request('DELETE', `/api/registries/${encodeURIComponent(url)}`);
|
|
}
|
|
|
|
async listRegistries(): Promise<IRegistry[]> {
|
|
return await this.request<IRegistry[]>('GET', '/api/registries');
|
|
}
|
|
|
|
// ============ DNS Operations ============
|
|
|
|
async addDnsRecord(domain: string): Promise<void> {
|
|
await this.request('POST', '/api/dns', { domain });
|
|
}
|
|
|
|
async removeDnsRecord(domain: string): Promise<void> {
|
|
await this.request('DELETE', `/api/dns/${domain}`);
|
|
}
|
|
|
|
async listDnsRecords(): Promise<IDnsRecord[]> {
|
|
return await this.request<IDnsRecord[]>('GET', '/api/dns');
|
|
}
|
|
|
|
async syncDns(): Promise<void> {
|
|
await this.request('POST', '/api/dns/sync');
|
|
}
|
|
|
|
// ============ SSL Operations ============
|
|
|
|
async renewCertificate(domain?: string): Promise<void> {
|
|
const path = domain ? `/api/ssl/renew/${domain}` : '/api/ssl/renew';
|
|
await this.request('POST', path);
|
|
}
|
|
|
|
async listCertificates(): Promise<ISslCertificate[]> {
|
|
return await this.request<ISslCertificate[]>('GET', '/api/ssl');
|
|
}
|
|
|
|
async forceRenewCertificate(domain: string): Promise<void> {
|
|
await this.request('POST', `/api/ssl/renew/${domain}?force=true`);
|
|
}
|
|
|
|
// ============ Nginx Operations ============
|
|
|
|
async reloadNginx(): Promise<void> {
|
|
await this.request('POST', '/api/nginx/reload');
|
|
}
|
|
|
|
async testNginx(): Promise<{ success: boolean; output: string }> {
|
|
return await this.request('POST', '/api/nginx/test');
|
|
}
|
|
|
|
async getNginxStatus(): Promise<{ status: string }> {
|
|
return await this.request('GET', '/api/nginx/status');
|
|
}
|
|
|
|
// ============ Config Operations ============
|
|
|
|
async getSettings(): Promise<Record<string, string>> {
|
|
return await this.request<Record<string, string>>('GET', '/api/config');
|
|
}
|
|
|
|
async setSetting(key: string, value: string): Promise<void> {
|
|
await this.request('POST', '/api/config', { key, value });
|
|
}
|
|
|
|
// ============ System Operations ============
|
|
|
|
async getStatus(): Promise<{
|
|
services: { total: number; running: number; stopped: number };
|
|
uptime: number;
|
|
}> {
|
|
return await this.request('GET', '/api/status');
|
|
}
|
|
|
|
// ============ Helper Methods ============
|
|
|
|
/**
|
|
* Make HTTP request to daemon
|
|
*/
|
|
private async request<T = unknown>(
|
|
method: string,
|
|
path: string,
|
|
body?: unknown
|
|
): Promise<T> {
|
|
const url = `${this.baseUrl}${path}`;
|
|
|
|
const headers: Record<string, string> = {
|
|
'Content-Type': 'application/json',
|
|
};
|
|
|
|
if (this.token) {
|
|
headers['Authorization'] = `Bearer ${this.token}`;
|
|
}
|
|
|
|
const options: RequestInit = {
|
|
method,
|
|
headers,
|
|
signal: AbortSignal.timeout(30000), // 30 second timeout
|
|
};
|
|
|
|
if (body) {
|
|
options.body = JSON.stringify(body);
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(url, options);
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({ message: response.statusText }));
|
|
throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
// For DELETE and some POST requests, there might be no content
|
|
if (response.status === 204 || response.headers.get('content-length') === '0') {
|
|
return undefined as T;
|
|
}
|
|
|
|
return await response.json();
|
|
} catch (error) {
|
|
if (error.name === 'TimeoutError') {
|
|
throw new Error('Request timed out. Daemon might be unresponsive.');
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set authentication token
|
|
*/
|
|
setToken(token: string): void {
|
|
this.token = token;
|
|
}
|
|
}
|