diff --git a/.gitignore b/.gitignore index a313ed3..fe5f1f6 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,5 @@ config.local.json # Logs logs/ *.log + +.playwright-mcp diff --git a/.playwright-mcp/settings-acme-section.png b/.playwright-mcp/settings-acme-section.png deleted file mode 100644 index b2b28eb..0000000 Binary files a/.playwright-mcp/settings-acme-section.png and /dev/null differ diff --git a/.playwright-mcp/settings-after-reload.png b/.playwright-mcp/settings-after-reload.png deleted file mode 100644 index 183075c..0000000 Binary files a/.playwright-mcp/settings-after-reload.png and /dev/null differ diff --git a/.playwright-mcp/settings-fixed-persistence.png b/.playwright-mcp/settings-fixed-persistence.png deleted file mode 100644 index 49e68a4..0000000 Binary files a/.playwright-mcp/settings-fixed-persistence.png and /dev/null differ diff --git a/ts/classes/httpserver.ts b/ts/classes/httpserver.ts index 428edde..f290a5f 100644 --- a/ts/classes/httpserver.ts +++ b/ts/classes/httpserver.ts @@ -224,6 +224,13 @@ export class OneboxHttpServer { } else if (path.match(/^\/api\/ssl\/[^/]+\/renew$/) && method === 'POST') { const domain = path.split('/')[3]; return await this.handleRenewCertificateRequest(domain); + } else if (path === '/api/domains' && method === 'GET') { + return await this.handleGetDomainsRequest(); + } else if (path === '/api/domains/sync' && method === 'POST') { + return await this.handleSyncDomainsRequest(); + } else if (path.match(/^\/api\/domains\/[^/]+$/) && method === 'GET') { + const domainName = path.split('/').pop()!; + return await this.handleGetDomainDetailRequest(domainName); } else { return this.jsonResponse({ success: false, error: 'Not found' }, 404); } diff --git a/ts/classes/ssl.ts b/ts/classes/ssl.ts index 21f3488..5030528 100644 --- a/ts/classes/ssl.ts +++ b/ts/classes/ssl.ts @@ -89,6 +89,52 @@ export class OneboxSslManager { return this.smartacme !== null && this.acmeEmail !== null; } + /** + * Acquire certificate and return certificate data (for CertRequirementManager) + * Returns certificate paths and expiry information + */ + async acquireCertificate( + domain: string, + includeWildcard = false + ): Promise<{ + certPath: string; + keyPath: string; + fullChainPath: string; + expiryDate: number; + issuer: string; + }> { + try { + if (!this.isConfigured()) { + throw new Error('SSL manager not configured'); + } + + logger.info(`Acquiring SSL certificate for ${domain} via SmartACME DNS-01...`); + + // Use SmartACME to obtain certificate via DNS-01 challenge + const cert = await this.smartacme!.getCertificateForDomain(domain, { + includeWildcard, + }); + + logger.success(`SSL certificate obtained for ${domain}`); + logger.info(`Certificate valid until: ${new Date(cert.validUntil).toISOString()}`); + + // Reload certificates in reverse proxy + await this.oneboxRef.reverseProxy.reloadCertificates(); + + // Return certificate data + return { + certPath: cert.certFilePath, + keyPath: cert.keyFilePath, + fullChainPath: cert.chainFilePath || cert.certFilePath, + expiryDate: cert.validUntil, + issuer: cert.issuer || 'Let\'s Encrypt', + }; + } catch (error) { + logger.error(`Failed to acquire certificate for ${domain}: ${error.message}`); + throw error; + } + } + /** * Obtain SSL certificate for a domain using SmartACME */