netget
Version:
Rette Adepto/ Recibido Directamente.
358 lines (304 loc) • 11.8 kB
text/typescript
// netgetSync.ts
import fs from 'fs/promises';
import { createWriteStream, createReadStream } from 'fs';
import sqlite3 from 'sqlite3';
import axios, { AxiosError } from 'axios';
import FormData from 'form-data';
import archiver from 'archiver';
import path from 'path';
interface SyncConfig {
localDbPath: string;
remoteServer: string;
remoteApiKey: string;
projectsBasePath: string;
}
interface DomainConfig {
domain: string;
subdomain: string;
email: string;
sslMode: string;
sslCertificate: string;
sslCertificateKey: string;
target: string;
type: string;
projectPath: string;
owner: string;
}
interface SyncOptions {
includeProjects?: boolean;
domains?: string[] | null;
}
interface SyncResult {
success: boolean;
syncedDomains?: number;
message?: string;
error?: string;
}
interface HealthCheckResult {
status: string;
database?: string;
openresty?: string;
error?: string;
timestamp: string;
}
interface CompareResult {
local: DomainConfig[];
remote: DomainConfig[];
onlyLocal: DomainConfig[];
onlyRemote: DomainConfig[];
common: DomainConfig[];
}
export class NetGetSync {
private localDbPath: string;
private remoteServer: string;
private remoteApiKey: string;
private projectsBasePath: string;
constructor(config: SyncConfig) {
this.localDbPath = config.localDbPath;
this.remoteServer = config.remoteServer;
this.remoteApiKey = config.remoteApiKey;
this.projectsBasePath = config.projectsBasePath;
}
/**
* Read local NetGet configuration from SQLite database
*/
async readLocalConfig(): Promise<DomainConfig[]> {
return new Promise((resolve, reject) => {
const db = new sqlite3.Database(this.localDbPath, sqlite3.OPEN_READONLY, (err) => {
if (err) {
reject(new Error(`Failed to open local database: ${err.message}`));
return;
}
});
const query = `
SELECT domain, subdomain, email, sslMode, sslCertificate, sslCertificateKey,
target, type, projectPath, owner
FROM domains
ORDER BY domain
`;
db.all(query, [], (err, rows: DomainConfig[]) => {
if (err) {
reject(new Error(`Failed to read domains: ${err.message}`));
return;
}
db.close();
resolve(rows);
});
});
}
/**
* Package project files for deployment
*/
async packageProject(target: string, domain: string): Promise<string> {
const tempDir = path.join(process.cwd(), 'temp');
await fs.mkdir(tempDir, { recursive: true });
const zipPath = path.join(tempDir, `${domain}.zip`);
return new Promise((resolve, reject) => {
const output = createWriteStream(zipPath);
const archive = archiver('zip', { zlib: { level: 9 } });
output.on('close', () => {
console.log(`✓ Project packaged: ${archive.pointer()} bytes`);
resolve(zipPath);
});
archive.on('error', (err) => {
reject(err);
});
archive.pipe(output);
// Add project files to archive (exclude node_modules, .git, etc.)
archive.glob('**/*', {
cwd: target,
ignore: [
'node_modules/**',
'.git/**',
'.env',
'*.log',
'dist/**',
'build/**',
'.DS_Store'
]
});
archive.finalize();
});
}
/**
* Sync domain configuration to remote server
*/
async syncDomainConfig(domains: DomainConfig[]): Promise<any> {
try {
const response = await axios.post(`${this.remoteServer}/deploy/sync/domains`, {
domains: domains,
timestamp: Date.now()
}, {
headers: {
'Authorization': `Bearer ${this.remoteApiKey}`,
'Content-Type': 'application/json'
}
});
return response.data;
} catch (error: any) {
const axiosError = error as AxiosError;
throw new Error(`Failed to sync domain config: ${axiosError.response?.data || axiosError.message}`);
}
}
/**
* Deploy project files to remote server
*/
async deployProject(domain: string, zipPath: string): Promise<any> {
try {
const form = new FormData();
form.append('domain', domain);
form.append('file', createReadStream(zipPath));
const response = await axios.post(`${this.remoteServer}/deploy/sync/deploy`, form, {
headers: {
...form.getHeaders(),
'Authorization': `Bearer ${this.remoteApiKey}`
},
maxContentLength: Infinity,
maxBodyLength: Infinity,
timeout: 300000 // 5 minutes
});
return response.data;
} catch (error: any) {
const axiosError = error as AxiosError;
// Gather more error details
let details = '';
if (axiosError.response) {
details += `\nStatus: ${axiosError.response.status}`;
details += `\nResponse data: ${JSON.stringify(axiosError.response.data)}`;
details += `\nHeaders: ${JSON.stringify(axiosError.response.headers)}`;
}
if (axiosError.code) {
details += `\nError code: ${axiosError.code}`;
}
if (axiosError.stack) {
details += `\nStack trace: ${axiosError.stack}`;
}
throw new Error(`Failed to deploy project: ${axiosError.message}${details}`);
}
}
/**
* Check remote server health
*/
async checkRemoteHealth(): Promise<HealthCheckResult> {
try {
const response = await axios.get(`${this.remoteServer}/deploy/health`, {
headers: {
Authorization: `Bearer ${this.remoteApiKey}`
}
});
return response.data;
} catch (error: any) {
const axiosError = error as AxiosError;
return {
status: 'unhealthy',
error: axiosError.message,
timestamp: new Date().toISOString()
};
}
}
/**
* Main sync process
*/
async sync(options: SyncOptions = {}): Promise<SyncResult> {
const { includeProjects = false, domains: specificDomains = null } = options;
console.log('🚀 Starting NetGet sync process...\n');
try {
// 1. Check remote server health
console.log('1. Checking remote server health...');
await this.checkRemoteHealth();
console.log('✓ Remote server is accessible\n');
// 2. Read local configuration
console.log('2. Reading local NetGet configuration...');
const allDomains = await this.readLocalConfig();
// Filter domains if specific domains are requested
const domainsToSync = specificDomains
? allDomains.filter(d => specificDomains.includes(d.domain))
: allDomains;
console.log(`✓ Found ${domainsToSync.length} domains to sync\n`);
// 3. Sync domain configurations
console.log('3. Syncing domain configurations...');
const syncResult = await this.syncDomainConfig(domainsToSync);
console.log(`✓ Configuration synced: ${syncResult.message}\n`);
// 4. Deploy projects if requested
if (includeProjects) {
console.log('4. Deploying project files...');
for (const domain of domainsToSync) {
if (domain.target && domain.type !== 'server') {
try {
console.log(` 📦 Packaging ${domain.domain}...`);
const zipPath = await this.packageProject(domain.target, domain.domain);
console.log(` 🚀 Deploying ${domain.domain}...`);
await this.deployProject(domain.domain, zipPath);
// Clean up temp file
await fs.unlink(zipPath);
console.log(` ✓ ${domain.domain} deployed successfully`);
} catch (error: any) {
console.log(` ❌ Failed to deploy ${domain.domain}: ${error.message}`);
}
}
}
console.log('');
}
console.log('🎉 NetGet sync completed successfully!');
return {
success: true,
syncedDomains: domainsToSync.length,
message: 'Sync completed successfully'
};
} catch (error: any) {
console.error(`❌ Sync failed: ${error.message}`);
return {
success: false,
error: error.message
};
}
}
/**
* Compare local and remote configurations
*/
async compare(): Promise<CompareResult> {
try {
console.log('🔍 Comparing local and remote configurations...\n');
// Get local config
const localDomains = await this.readLocalConfig();
// Get remote config
const response = await axios.get(`${this.remoteServer}/deploy/sync/domains`, {
headers: {
'Authorization': `Bearer ${this.remoteApiKey}`
}
});
const remoteDomains: DomainConfig[] = response.data.domains;
// Compare
const localDomainNames = new Set(localDomains.map(d => d.domain));
const remoteDomainNames = new Set(remoteDomains.map(d => d.domain));
const onlyLocal = localDomains.filter(d => !remoteDomainNames.has(d.domain));
const onlyRemote = remoteDomains.filter(d => !localDomainNames.has(d.domain));
const common = localDomains.filter(d => remoteDomainNames.has(d.domain));
console.log(`📊 Comparison Results:`);
console.log(` Local domains: ${localDomains.length}`);
console.log(` Remote domains: ${remoteDomains.length}`);
console.log(` Only in local: ${onlyLocal.length}`);
console.log(` Only in remote: ${onlyRemote.length}`);
console.log(` Common: ${common.length}\n`);
if (onlyLocal.length > 0) {
console.log('🔸 Domains only in local:');
onlyLocal.forEach(d => console.log(` - ${d.domain}`));
console.log('');
}
if (onlyRemote.length > 0) {
console.log('🔹 Domains only in remote:');
onlyRemote.forEach(d => console.log(` - ${d.domain}`));
console.log('');
}
return {
local: localDomains,
remote: remoteDomains,
onlyLocal,
onlyRemote,
common
};
} catch (error: any) {
throw new Error(`Failed to compare configurations: ${error.message}`);
}
}
}