diff --git a/apps/api/src/domains/backup/backup.repository.ts b/apps/api/src/domains/backup/backup.repository.ts index 937988a..d82db47 100644 --- a/apps/api/src/domains/backup/backup.repository.ts +++ b/apps/api/src/domains/backup/backup.repository.ts @@ -379,6 +379,46 @@ export class BackupRepository { }, }); } + + /** + * Get all Network Load Balancers for backup + */ + async getAllNetworkLoadBalancers() { + return prisma.networkLoadBalancer.findMany({ + include: { + upstreams: true, + }, + }); + } + + /** + * Upsert Network Load Balancer + */ + async upsertNetworkLoadBalancer(name: string, createData: any, updateData: any) { + return prisma.networkLoadBalancer.upsert({ + where: { name }, + update: updateData, + create: { name, ...createData }, + }); + } + + /** + * Create NLB upstream + */ + async createNLBUpstream(data: Prisma.NLBUpstreamCreateInput) { + return prisma.nLBUpstream.create({ + data, + }); + } + + /** + * Delete NLB upstreams by NLB ID + */ + async deleteNLBUpstreamsByNLBId(nlbId: string) { + return prisma.nLBUpstream.deleteMany({ + where: { nlbId }, + }); + } } // Export singleton instance diff --git a/apps/api/src/domains/backup/backup.service.ts b/apps/api/src/domains/backup/backup.service.ts index a1d5ee6..57d408c 100644 --- a/apps/api/src/domains/backup/backup.service.ts +++ b/apps/api/src/domains/backup/backup.service.ts @@ -272,6 +272,8 @@ export class BackupService { alertRules: 0, users: 0, nginxConfigs: 0, + networkLoadBalancers: 0, + nlbUpstreams: 0, }; // 1. Restore domains @@ -328,6 +330,13 @@ export class BackupService { } } + // 9. Restore Network Load Balancers + if (backupData.networkLoadBalancers && Array.isArray(backupData.networkLoadBalancers)) { + for (const nlbData of backupData.networkLoadBalancers) { + await this.restoreNetworkLoadBalancer(nlbData, results); + } + } + logger.info('Configuration imported successfully', { userId, results }); // Reload nginx @@ -465,6 +474,9 @@ export class BackupService { // Get nginx configs const nginxConfigs = await backupRepository.getAllNginxConfigs(); + // Get Network Load Balancers + const networkLoadBalancers = await backupRepository.getAllNetworkLoadBalancers(); + return { version: BACKUP_CONSTANTS.BACKUP_VERSION, timestamp: new Date().toISOString(), @@ -497,6 +509,36 @@ export class BackupService { })), users, nginxConfigs, + networkLoadBalancers: networkLoadBalancers.map((nlb) => ({ + name: nlb.name, + description: nlb.description || undefined, + port: nlb.port, + protocol: nlb.protocol, + algorithm: nlb.algorithm, + status: nlb.status, + enabled: nlb.enabled, + proxyTimeout: nlb.proxyTimeout, + proxyConnectTimeout: nlb.proxyConnectTimeout, + proxyNextUpstream: nlb.proxyNextUpstream, + proxyNextUpstreamTimeout: nlb.proxyNextUpstreamTimeout, + proxyNextUpstreamTries: nlb.proxyNextUpstreamTries, + healthCheckEnabled: nlb.healthCheckEnabled, + healthCheckInterval: nlb.healthCheckInterval, + healthCheckTimeout: nlb.healthCheckTimeout, + healthCheckRises: nlb.healthCheckRises, + healthCheckFalls: nlb.healthCheckFalls, + upstreams: nlb.upstreams.map((u) => ({ + host: u.host, + port: u.port, + weight: u.weight, + maxFails: u.maxFails, + failTimeout: u.failTimeout, + maxConns: u.maxConns, + backup: u.backup, + down: u.down, + status: u.status, + })), + })), }; } @@ -960,6 +1002,80 @@ export class BackupService { } } + /** + * Restore Network Load Balancer + */ + private async restoreNetworkLoadBalancer(nlbData: any, results: ImportResults) { + try { + const nlb = await backupRepository.upsertNetworkLoadBalancer( + nlbData.name, + { + description: nlbData.description, + port: nlbData.port, + protocol: nlbData.protocol, + algorithm: nlbData.algorithm, + status: nlbData.status || 'inactive', + enabled: nlbData.enabled ?? true, + proxyTimeout: nlbData.proxyTimeout ?? 3, + proxyConnectTimeout: nlbData.proxyConnectTimeout ?? 1, + proxyNextUpstream: nlbData.proxyNextUpstream ?? true, + proxyNextUpstreamTimeout: nlbData.proxyNextUpstreamTimeout ?? 0, + proxyNextUpstreamTries: nlbData.proxyNextUpstreamTries ?? 0, + healthCheckEnabled: nlbData.healthCheckEnabled ?? true, + healthCheckInterval: nlbData.healthCheckInterval ?? 10, + healthCheckTimeout: nlbData.healthCheckTimeout ?? 5, + healthCheckRises: nlbData.healthCheckRises ?? 2, + healthCheckFalls: nlbData.healthCheckFalls ?? 3, + }, + { + description: nlbData.description, + port: nlbData.port, + protocol: nlbData.protocol, + algorithm: nlbData.algorithm, + status: nlbData.status || 'inactive', + enabled: nlbData.enabled ?? true, + proxyTimeout: nlbData.proxyTimeout ?? 3, + proxyConnectTimeout: nlbData.proxyConnectTimeout ?? 1, + proxyNextUpstream: nlbData.proxyNextUpstream ?? true, + proxyNextUpstreamTimeout: nlbData.proxyNextUpstreamTimeout ?? 0, + proxyNextUpstreamTries: nlbData.proxyNextUpstreamTries ?? 0, + healthCheckEnabled: nlbData.healthCheckEnabled ?? true, + healthCheckInterval: nlbData.healthCheckInterval ?? 10, + healthCheckTimeout: nlbData.healthCheckTimeout ?? 5, + healthCheckRises: nlbData.healthCheckRises ?? 2, + healthCheckFalls: nlbData.healthCheckFalls ?? 3, + } + ); + + results.networkLoadBalancers++; + + // Restore upstreams + if (nlbData.upstreams && Array.isArray(nlbData.upstreams)) { + await backupRepository.deleteNLBUpstreamsByNLBId(nlb.id); + + for (const upstream of nlbData.upstreams) { + await backupRepository.createNLBUpstream({ + nlb: { connect: { id: nlb.id } }, + host: upstream.host, + port: upstream.port, + weight: upstream.weight ?? 1, + maxFails: upstream.maxFails ?? 3, + failTimeout: upstream.failTimeout ?? 10, + maxConns: upstream.maxConns ?? 0, + backup: upstream.backup ?? false, + down: upstream.down ?? false, + status: upstream.status || 'checking', + }); + results.nlbUpstreams++; + } + } + + logger.info(`Network Load Balancer ${nlbData.name} restored`); + } catch (error) { + logger.error(`Failed to restore Network Load Balancer ${nlbData.name}:`, error); + } + } + /** * Generate nginx config for backup restore */ diff --git a/apps/api/src/domains/backup/backup.types.ts b/apps/api/src/domains/backup/backup.types.ts index 062ad44..d373318 100644 --- a/apps/api/src/domains/backup/backup.types.ts +++ b/apps/api/src/domains/backup/backup.types.ts @@ -62,6 +62,7 @@ export interface BackupData { alertRules: any[]; users: any[]; nginxConfigs: any[]; + networkLoadBalancers: NetworkLoadBalancerBackupData[]; } /** @@ -137,6 +138,8 @@ export interface ImportResults { alertRules: number; users: number; nginxConfigs: number; + networkLoadBalancers: number; + nlbUpstreams: number; } /** @@ -148,6 +151,45 @@ export interface SSLCertificateFiles { chain?: string; } +/** + * Network Load Balancer backup data + */ +export interface NetworkLoadBalancerBackupData { + name: string; + description?: string; + port: number; + protocol: string; + algorithm: string; + status: string; + enabled: boolean; + proxyTimeout: number; + proxyConnectTimeout: number; + proxyNextUpstream: boolean; + proxyNextUpstreamTimeout: number; + proxyNextUpstreamTries: number; + healthCheckEnabled: boolean; + healthCheckInterval: number; + healthCheckTimeout: number; + healthCheckRises: number; + healthCheckFalls: number; + upstreams: NetworkLoadBalancerUpstreamBackupData[]; +} + +/** + * Network Load Balancer upstream backup data + */ +export interface NetworkLoadBalancerUpstreamBackupData { + host: string; + port: number; + weight: number; + maxFails: number; + failTimeout: number; + maxConns: number; + backup: boolean; + down: boolean; + status: string; +} + /** * Backup constants */ diff --git a/apps/api/src/domains/cluster/cluster.repository.ts b/apps/api/src/domains/cluster/cluster.repository.ts index e053336..567fa1a 100644 --- a/apps/api/src/domains/cluster/cluster.repository.ts +++ b/apps/api/src/domains/cluster/cluster.repository.ts @@ -200,6 +200,9 @@ export class ClusterRepository { const acl = await prisma.aclRule.findMany(); const users = await prisma.user.findMany(); + // Get Network Load Balancers + const networkLoadBalancers = await this.getAllNetworkLoadBalancersForSync(); + return { // Domains (NO timestamps, NO IDs) domains: domains.map(d => ({ @@ -276,6 +279,36 @@ export class ClusterRepository { fullName: u.fullName, password: u.password, // Already hashed role: u.role + })), + + // Network Load Balancers (NO timestamps, NO IDs) + networkLoadBalancers: networkLoadBalancers.map(nlb => ({ + name: nlb.name, + description: nlb.description || undefined, + port: nlb.port, + protocol: nlb.protocol, + algorithm: nlb.algorithm, + enabled: nlb.enabled, + proxyTimeout: nlb.proxyTimeout, + proxyConnectTimeout: nlb.proxyConnectTimeout, + proxyNextUpstream: nlb.proxyNextUpstream, + proxyNextUpstreamTimeout: nlb.proxyNextUpstreamTimeout, + proxyNextUpstreamTries: nlb.proxyNextUpstreamTries, + healthCheckEnabled: nlb.healthCheckEnabled, + healthCheckInterval: nlb.healthCheckInterval, + healthCheckTimeout: nlb.healthCheckTimeout, + healthCheckRises: nlb.healthCheckRises, + healthCheckFalls: nlb.healthCheckFalls, + upstreams: nlb.upstreams.map(u => ({ + host: u.host, + port: u.port, + weight: u.weight, + maxFails: u.maxFails, + failTimeout: u.failTimeout, + maxConns: u.maxConns, + backup: u.backup, + down: u.down, + })), })) }; } @@ -293,6 +326,8 @@ export class ClusterRepository { modsecCustom: 0, acl: 0, users: 0, + networkLoadBalancers: 0, + nlbUpstreams: 0, totalChanges: 0 }; @@ -480,12 +515,97 @@ export class ClusterRepository { } } + // 7. Import Network Load Balancers + if (config.networkLoadBalancers && Array.isArray(config.networkLoadBalancers)) { + for (const nlbData of config.networkLoadBalancers) { + const nlb = await this.upsertNetworkLoadBalancerForSync(nlbData.name, { + description: nlbData.description, + port: nlbData.port, + protocol: nlbData.protocol as any, + algorithm: nlbData.algorithm as any, + enabled: nlbData.enabled, + proxyTimeout: nlbData.proxyTimeout, + proxyConnectTimeout: nlbData.proxyConnectTimeout, + proxyNextUpstream: nlbData.proxyNextUpstream, + proxyNextUpstreamTimeout: nlbData.proxyNextUpstreamTimeout, + proxyNextUpstreamTries: nlbData.proxyNextUpstreamTries, + healthCheckEnabled: nlbData.healthCheckEnabled, + healthCheckInterval: nlbData.healthCheckInterval, + healthCheckTimeout: nlbData.healthCheckTimeout, + healthCheckRises: nlbData.healthCheckRises, + healthCheckFalls: nlbData.healthCheckFalls, + }); + results.networkLoadBalancers++; + + // Import upstreams + if (nlbData.upstreams && Array.isArray(nlbData.upstreams)) { + await this.deleteNLBUpstreamsByNLBIdForSync(nlb.id); + + for (const upstream of nlbData.upstreams) { + await this.createNLBUpstreamForSync({ + nlbId: nlb.id, + host: upstream.host, + port: upstream.port, + weight: upstream.weight, + maxFails: upstream.maxFails, + failTimeout: upstream.failTimeout, + maxConns: upstream.maxConns, + backup: upstream.backup, + down: upstream.down, + }); + results.nlbUpstreams++; + } + } + } + } + results.totalChanges = results.domains + results.ssl + results.modsecCRS + - results.modsecCustom + results.acl + results.users; + results.modsecCustom + results.acl + results.users + + results.networkLoadBalancers; return results; } + /** + * Get all Network Load Balancers for sync + */ + async getAllNetworkLoadBalancersForSync() { + return prisma.networkLoadBalancer.findMany({ + include: { + upstreams: true, + }, + }); + } + + /** + * Upsert Network Load Balancer for sync + */ + async upsertNetworkLoadBalancerForSync(name: string, data: any) { + return prisma.networkLoadBalancer.upsert({ + where: { name }, + update: data, + create: { name, ...data }, + }); + } + + /** + * Create NLB upstream for sync + */ + async createNLBUpstreamForSync(data: any) { + return prisma.nLBUpstream.create({ + data, + }); + } + + /** + * Delete NLB upstreams by NLB ID for sync + */ + async deleteNLBUpstreamsByNLBIdForSync(nlbId: string) { + return prisma.nLBUpstream.deleteMany({ + where: { nlbId }, + }); + } + /** * Update system config last connected timestamp */ diff --git a/apps/api/src/domains/cluster/cluster.types.ts b/apps/api/src/domains/cluster/cluster.types.ts index c4adafc..3f431f4 100644 --- a/apps/api/src/domains/cluster/cluster.types.ts +++ b/apps/api/src/domains/cluster/cluster.types.ts @@ -74,6 +74,7 @@ export interface SyncConfigData { modsecCustomRules: SyncModSecCustomRule[]; aclRules: SyncACLRule[]; users: SyncUser[]; + networkLoadBalancers: SyncNetworkLoadBalancer[]; } /** @@ -175,6 +176,43 @@ export interface SyncUser { role: string; } +/** + * Sync Network Load Balancer + */ +export interface SyncNetworkLoadBalancer { + name: string; + description?: string; + port: number; + protocol: string; + algorithm: string; + enabled: boolean; + proxyTimeout: number; + proxyConnectTimeout: number; + proxyNextUpstream: boolean; + proxyNextUpstreamTimeout: number; + proxyNextUpstreamTries: number; + healthCheckEnabled: boolean; + healthCheckInterval: number; + healthCheckTimeout: number; + healthCheckRises: number; + healthCheckFalls: number; + upstreams: SyncNLBUpstream[]; +} + +/** + * Sync NLB Upstream + */ +export interface SyncNLBUpstream { + host: string; + port: number; + weight: number; + maxFails: number; + failTimeout: number; + maxConns: number; + backup: boolean; + down: boolean; +} + /** * Sync Export Response */ @@ -195,6 +233,8 @@ export interface ImportResults { modsecCustom: number; acl: number; users: number; + networkLoadBalancers: number; + nlbUpstreams: number; totalChanges: number; }