f40ef6b7c0
Align Cloudly with the current typedserver, smartconfig, smartstate, and Docker tooling releases so builds and Docker output stay compatible with the upgraded stack.
575 lines
22 KiB
TypeScript
575 lines
22 KiB
TypeScript
import * as plugins from '../plugins.js';
|
|
import { CloudlyTaskManager } from './classes.taskmanager.js';
|
|
import { logger } from '../logger.js';
|
|
|
|
const getErrorMessage = (errorArg: unknown) => errorArg instanceof Error ? errorArg.message : String(errorArg);
|
|
|
|
/**
|
|
* Create and register all predefined tasks
|
|
*/
|
|
export function createPredefinedTasks(taskManager: CloudlyTaskManager) {
|
|
|
|
// Cloudflare Domain Sync Task
|
|
const cfDomainSync = new plugins.taskbuffer.Task({
|
|
name: 'cloudflare-domain-sync',
|
|
taskFunction: async () => {
|
|
const execution = taskManager.getCurrentExecution('cloudflare-domain-sync');
|
|
try {
|
|
await execution?.addLog('Starting Cloudflare domain sync…', 'info');
|
|
const cf = taskManager.cloudlyRef.cloudflareConnector?.cloudflare;
|
|
if (!cf) {
|
|
await execution?.addLog('Cloudflare not configured; skipping sync.', 'warning');
|
|
return { created: 0, updated: 0, totalZones: 0 };
|
|
}
|
|
|
|
const zones = await cf.convenience.listZones();
|
|
await execution?.setMetric('totalZones', zones.length);
|
|
await execution?.addLog(`Fetched ${zones.length} zones from Cloudflare`, 'info');
|
|
|
|
let created = 0;
|
|
let updated = 0;
|
|
const now = Date.now();
|
|
|
|
for (const zone of zones) {
|
|
// zone fields from Cloudflare typings
|
|
const zoneName = (zone as any).name as string;
|
|
const zoneId = (zone as any).id as string;
|
|
const zoneStatus = ((zone as any).status || 'active') as 'active'|'pending'|'suspended'|'transferred'|'expired';
|
|
const nameServers: string[] = (zone as any).name_servers || [];
|
|
|
|
const existing = await taskManager.cloudlyRef.domainManager.CDomain.getDomainByName(zoneName);
|
|
if (existing) {
|
|
if (execution && (taskManager.isCancellationRequested(execution.id) || existing.data == null)) {
|
|
await execution?.addLog('Cancellation requested. Stopping CF sync…', 'warning');
|
|
break;
|
|
}
|
|
await execution?.addLog(`Updating domain: ${zoneName}`, 'info');
|
|
await taskManager.cloudlyRef.domainManager.CDomain.updateDomain(existing.id, {
|
|
status: zoneStatus as any,
|
|
nameservers: nameServers,
|
|
cloudflareZoneId: zoneId,
|
|
syncSource: 'cloudflare',
|
|
lastSyncAt: now,
|
|
activationState: existing.data.activationState || 'available',
|
|
});
|
|
updated++;
|
|
} else {
|
|
await execution?.addLog(`Creating domain: ${zoneName}`, 'info');
|
|
await taskManager.cloudlyRef.domainManager.CDomain.createDomain({
|
|
name: zoneName,
|
|
description: `Synced from Cloudflare zone ${zoneId}`,
|
|
status: zoneStatus as any,
|
|
verificationStatus: 'pending',
|
|
nameservers: nameServers,
|
|
autoRenew: true,
|
|
cloudflareZoneId: zoneId,
|
|
activationState: 'available',
|
|
syncSource: 'cloudflare',
|
|
lastSyncAt: now,
|
|
} as any);
|
|
created++;
|
|
}
|
|
}
|
|
|
|
await execution?.setMetric('created', created);
|
|
await execution?.setMetric('updated', updated);
|
|
await execution?.addLog(`Cloudflare sync done: ${created} created, ${updated} updated`, 'success');
|
|
return { created, updated, totalZones: zones.length };
|
|
} catch (error) {
|
|
await execution?.addLog(`Cloudflare sync error: ${getErrorMessage(error)}`, 'error');
|
|
throw error;
|
|
}
|
|
},
|
|
});
|
|
|
|
taskManager.registerTask('cloudflare-domain-sync', cfDomainSync, {
|
|
description: 'Import and update domains from Cloudflare zones',
|
|
category: 'system',
|
|
schedule: '0 3 * * *', // Daily at 3 AM
|
|
enabled: true,
|
|
});
|
|
|
|
// DNS Sync Task
|
|
const dnsSync = new plugins.taskbuffer.Task({
|
|
name: 'dns-sync',
|
|
taskFunction: async () => {
|
|
const execution = taskManager.getCurrentExecution('dns-sync');
|
|
const dnsManager = taskManager.cloudlyRef.dnsManager;
|
|
|
|
try {
|
|
await execution?.addLog('Starting DNS synchronization...', 'info');
|
|
if (execution && (taskManager.isCancellationRequested(execution.id) || execution.data.status === 'cancelled')) {
|
|
await execution.addLog('Cancellation requested. Aborting DNS sync...', 'warning');
|
|
return;
|
|
}
|
|
|
|
// Get all DNS entries marked as external
|
|
const dnsEntries = await dnsManager.CDnsEntry.getInstances({
|
|
'data.sourceType': 'external',
|
|
});
|
|
|
|
await execution?.addLog(`Found ${dnsEntries.length} external DNS entries to sync`, 'info');
|
|
await execution?.setMetric('totalEntries', dnsEntries.length);
|
|
|
|
let syncedCount = 0;
|
|
let failedCount = 0;
|
|
|
|
for (const entry of dnsEntries) {
|
|
if (execution && (taskManager.isCancellationRequested(execution.id) || execution.data.status === 'cancelled')) {
|
|
await execution.addLog('Cancellation requested. Stopping loop...', 'warning');
|
|
break;
|
|
}
|
|
try {
|
|
// TODO: Implement actual sync with external DNS provider
|
|
await execution?.addLog(`Syncing DNS entry: ${entry.data.name}.${entry.data.zone}`, 'info');
|
|
syncedCount++;
|
|
} catch (error) {
|
|
await execution?.addLog(`Failed to sync ${entry.data.name}: ${getErrorMessage(error)}`, 'warning');
|
|
failedCount++;
|
|
}
|
|
}
|
|
|
|
await execution?.setMetric('syncedCount', syncedCount);
|
|
await execution?.setMetric('failedCount', failedCount);
|
|
await execution?.addLog(`DNS sync completed: ${syncedCount} synced, ${failedCount} failed`, 'success');
|
|
|
|
return { synced: syncedCount, failed: failedCount };
|
|
} catch (error) {
|
|
await execution?.addLog(`DNS sync error: ${getErrorMessage(error)}`, 'error');
|
|
throw error;
|
|
}
|
|
},
|
|
});
|
|
|
|
taskManager.registerTask('dns-sync', dnsSync, {
|
|
description: 'Synchronize DNS entries with external providers',
|
|
category: 'system',
|
|
schedule: '0 */6 * * *', // Every 6 hours
|
|
enabled: true,
|
|
});
|
|
|
|
// Certificate Renewal Task
|
|
const certRenewal = new plugins.taskbuffer.Task({
|
|
name: 'cert-renewal',
|
|
taskFunction: async () => {
|
|
const execution = taskManager.getCurrentExecution('cert-renewal');
|
|
|
|
try {
|
|
await execution?.addLog('Checking certificates for renewal...', 'info');
|
|
if (execution && (taskManager.isCancellationRequested(execution.id) || execution.data.status === 'cancelled')) {
|
|
await execution.addLog('Cancellation requested. Aborting certificate renewal...', 'warning');
|
|
return;
|
|
}
|
|
|
|
// Get all domains (only activated ones are considered for renewal)
|
|
const domains = await taskManager.cloudlyRef.domainManager.CDomain.getInstances({
|
|
'data.activationState': 'activated',
|
|
} as any);
|
|
await execution?.setMetric('totalDomains', domains.length);
|
|
|
|
let renewedCount = 0;
|
|
let upToDateCount = 0;
|
|
|
|
for (const domain of domains) {
|
|
if (execution && (taskManager.isCancellationRequested(execution.id) || execution.data.status === 'cancelled')) {
|
|
await execution.addLog('Cancellation requested. Stopping loop...', 'warning');
|
|
break;
|
|
}
|
|
// TODO: Check certificate expiry and renew if needed
|
|
await execution?.addLog(`Checking certificate for ${domain.data.name}`, 'info');
|
|
|
|
// Placeholder logic
|
|
const needsRenewal = Math.random() > 0.8; // 20% chance for demo
|
|
|
|
if (needsRenewal) {
|
|
await execution?.addLog(`Renewing certificate for ${domain.data.name}`, 'info');
|
|
// TODO: Actual renewal logic
|
|
renewedCount++;
|
|
} else {
|
|
upToDateCount++;
|
|
}
|
|
}
|
|
|
|
await execution?.setMetric('renewedCount', renewedCount);
|
|
await execution?.setMetric('upToDateCount', upToDateCount);
|
|
await execution?.addLog(`Certificate check completed: ${renewedCount} renewed, ${upToDateCount} up to date`, 'success');
|
|
|
|
return { renewed: renewedCount, upToDate: upToDateCount };
|
|
} catch (error) {
|
|
await execution?.addLog(`Certificate renewal error: ${getErrorMessage(error)}`, 'error');
|
|
throw error;
|
|
}
|
|
},
|
|
});
|
|
|
|
taskManager.registerTask('cert-renewal', certRenewal, {
|
|
description: 'Check and renew SSL certificates',
|
|
category: 'security',
|
|
schedule: '0 2 * * *', // Daily at 2 AM
|
|
enabled: true,
|
|
});
|
|
|
|
// Cleanup Task
|
|
const cleanup = new plugins.taskbuffer.Task({
|
|
name: 'cleanup',
|
|
taskFunction: async () => {
|
|
const execution = taskManager.getCurrentExecution('cleanup');
|
|
|
|
try {
|
|
await execution?.addLog('Starting cleanup tasks...', 'info');
|
|
if (execution && (taskManager.isCancellationRequested(execution.id) || execution.data.status === 'cancelled')) {
|
|
await execution.addLog('Cancellation requested. Aborting cleanup...', 'warning');
|
|
return;
|
|
}
|
|
|
|
// Clean up old task executions
|
|
await execution?.addLog('Cleaning old task executions...', 'info');
|
|
const deletedExecutions = await taskManager.CTaskExecution.cleanupOldExecutions(30);
|
|
await execution?.setMetric('deletedExecutions', deletedExecutions);
|
|
if (execution && (taskManager.isCancellationRequested(execution.id) || execution.data.status === 'cancelled')) return;
|
|
|
|
// TODO: Clean up old logs
|
|
await execution?.addLog('Cleaning old logs...', 'info');
|
|
// Placeholder
|
|
const deletedLogs = 0;
|
|
await execution?.setMetric('deletedLogs', deletedLogs);
|
|
if (execution && (taskManager.isCancellationRequested(execution.id) || execution.data.status === 'cancelled')) return;
|
|
|
|
// TODO: Clean up Docker images
|
|
await execution?.addLog('Cleaning unused Docker images...', 'info');
|
|
// Placeholder
|
|
const deletedImages = 0;
|
|
await execution?.setMetric('deletedImages', deletedImages);
|
|
|
|
await execution?.addLog(`Cleanup completed: ${deletedExecutions} executions, ${deletedLogs} logs, ${deletedImages} images deleted`, 'success');
|
|
|
|
return {
|
|
executions: deletedExecutions,
|
|
logs: deletedLogs,
|
|
images: deletedImages,
|
|
};
|
|
} catch (error) {
|
|
await execution?.addLog(`Cleanup error: ${getErrorMessage(error)}`, 'error');
|
|
throw error;
|
|
}
|
|
},
|
|
});
|
|
|
|
taskManager.registerTask('cleanup', cleanup, {
|
|
description: 'Remove old logs, executions, and temporary files',
|
|
category: 'cleanup',
|
|
schedule: '0 3 * * *', // Daily at 3 AM
|
|
enabled: true,
|
|
});
|
|
|
|
// Health Check Task
|
|
const healthCheck = new plugins.taskbuffer.Task({
|
|
name: 'health-check',
|
|
taskFunction: async () => {
|
|
const execution = taskManager.getCurrentExecution('health-check');
|
|
|
|
try {
|
|
await execution?.addLog('Starting health checks...', 'info');
|
|
if (execution && (taskManager.isCancellationRequested(execution.id) || execution.data.status === 'cancelled')) {
|
|
await execution.addLog('Cancellation requested. Aborting health checks...', 'warning');
|
|
return;
|
|
}
|
|
|
|
// Check all deployments
|
|
const deployments = await taskManager.cloudlyRef.deploymentManager.getAllDeployments();
|
|
await execution?.setMetric('totalDeployments', deployments.length);
|
|
|
|
let healthyCount = 0;
|
|
let unhealthyCount = 0;
|
|
const issues: Array<{ deploymentId: string; serviceId: string; issue: string }> = [];
|
|
|
|
for (const deployment of deployments) {
|
|
if (execution && (taskManager.isCancellationRequested(execution.id) || execution.data.status === 'cancelled')) {
|
|
await execution.addLog('Cancellation requested. Stopping loop...', 'warning');
|
|
break;
|
|
}
|
|
if (deployment.status === 'running') {
|
|
// TODO: Actual health check logic
|
|
const isHealthy = Math.random() > 0.1; // 90% healthy for demo
|
|
|
|
if (isHealthy) {
|
|
healthyCount++;
|
|
} else {
|
|
unhealthyCount++;
|
|
issues.push({
|
|
deploymentId: deployment.id,
|
|
serviceId: deployment.serviceId,
|
|
issue: 'Health check failed',
|
|
});
|
|
await execution?.addLog(`Deployment ${deployment.id} is unhealthy`, 'warning');
|
|
}
|
|
}
|
|
}
|
|
|
|
await execution?.setMetric('healthyCount', healthyCount);
|
|
await execution?.setMetric('unhealthyCount', unhealthyCount);
|
|
await execution?.setMetric('issues', issues);
|
|
|
|
const severity = unhealthyCount > 0 ? 'warning' : 'success';
|
|
await execution?.addLog(
|
|
`Health check completed: ${healthyCount} healthy, ${unhealthyCount} unhealthy`,
|
|
severity as any
|
|
);
|
|
|
|
return { healthy: healthyCount, unhealthy: unhealthyCount, issues };
|
|
} catch (error) {
|
|
await execution?.addLog(`Health check error: ${getErrorMessage(error)}`, 'error');
|
|
throw error;
|
|
}
|
|
},
|
|
});
|
|
|
|
taskManager.registerTask('health-check', healthCheck, {
|
|
description: 'Monitor service health across deployments',
|
|
category: 'monitoring',
|
|
schedule: '*/15 * * * *', // Every 15 minutes
|
|
enabled: true,
|
|
});
|
|
|
|
// Resource Usage Report
|
|
const resourceReport = new plugins.taskbuffer.Task({
|
|
name: 'resource-report',
|
|
taskFunction: async () => {
|
|
const execution = taskManager.getCurrentExecution('resource-report');
|
|
|
|
try {
|
|
await execution?.addLog('Generating resource usage report...', 'info');
|
|
if (execution && (taskManager.isCancellationRequested(execution.id) || execution.data.status === 'cancelled')) {
|
|
await execution.addLog('Cancellation requested. Aborting report...', 'warning');
|
|
return;
|
|
}
|
|
|
|
// Get all nodes
|
|
const nodes = await taskManager.cloudlyRef.nodeManager.CClusterNode.getInstances({});
|
|
|
|
const report: {
|
|
timestamp: number;
|
|
nodes: Array<{ nodeId: string; nodeName: string; cpu: number; memory: number; disk: number }>;
|
|
totalCpu: number;
|
|
totalMemory: number;
|
|
totalDisk: number;
|
|
} = {
|
|
timestamp: Date.now(),
|
|
nodes: [],
|
|
totalCpu: 0,
|
|
totalMemory: 0,
|
|
totalDisk: 0,
|
|
};
|
|
|
|
for (const node of nodes) {
|
|
if (execution && (taskManager.isCancellationRequested(execution.id) || execution.data.status === 'cancelled')) {
|
|
await execution.addLog('Cancellation requested. Stopping loop...', 'warning');
|
|
break;
|
|
}
|
|
// TODO: Get actual resource usage
|
|
const nodeUsage = {
|
|
nodeId: node.id,
|
|
nodeName: node.data.swarmNodeId || node.id,
|
|
cpu: Math.random() * 100, // Placeholder
|
|
memory: Math.random() * 100, // Placeholder
|
|
disk: Math.random() * 100, // Placeholder
|
|
};
|
|
|
|
report.nodes.push(nodeUsage);
|
|
report.totalCpu += nodeUsage.cpu;
|
|
report.totalMemory += nodeUsage.memory;
|
|
report.totalDisk += nodeUsage.disk;
|
|
}
|
|
|
|
// Calculate averages
|
|
if (nodes.length > 0) {
|
|
report.totalCpu /= nodes.length;
|
|
report.totalMemory /= nodes.length;
|
|
report.totalDisk /= nodes.length;
|
|
}
|
|
|
|
await execution?.setMetric('report', report);
|
|
await execution?.addLog(
|
|
`Resource report generated: Avg CPU ${report.totalCpu.toFixed(1)}%, Memory ${report.totalMemory.toFixed(1)}%, Disk ${report.totalDisk.toFixed(1)}%`,
|
|
'success'
|
|
);
|
|
|
|
return report;
|
|
} catch (error) {
|
|
await execution?.addLog(`Resource report error: ${getErrorMessage(error)}`, 'error');
|
|
throw error;
|
|
}
|
|
},
|
|
});
|
|
|
|
taskManager.registerTask('resource-report', resourceReport, {
|
|
description: 'Generate resource usage reports',
|
|
category: 'monitoring',
|
|
schedule: '0 * * * *', // Every hour
|
|
enabled: true,
|
|
});
|
|
|
|
// Database Maintenance
|
|
const dbMaintenance = new plugins.taskbuffer.Task({
|
|
name: 'db-maintenance',
|
|
taskFunction: async () => {
|
|
const execution = taskManager.getCurrentExecution('db-maintenance');
|
|
|
|
try {
|
|
await execution?.addLog('Starting database maintenance...', 'info');
|
|
if (execution && (taskManager.isCancellationRequested(execution.id) || execution.data.status === 'cancelled')) {
|
|
await execution.addLog('Cancellation requested. Aborting DB maintenance...', 'warning');
|
|
return;
|
|
}
|
|
|
|
// TODO: Implement actual database maintenance
|
|
await execution?.addLog('Analyzing indexes...', 'info');
|
|
if (execution && (taskManager.isCancellationRequested(execution.id) || execution.data.status === 'cancelled')) return;
|
|
await execution?.addLog('Compacting collections...', 'info');
|
|
if (execution && (taskManager.isCancellationRequested(execution.id) || execution.data.status === 'cancelled')) return;
|
|
await execution?.addLog('Updating statistics...', 'info');
|
|
|
|
await execution?.setMetric('collectionsOptimized', 5); // Placeholder
|
|
await execution?.setMetric('indexesRebuilt', 3); // Placeholder
|
|
|
|
await execution?.addLog('Database maintenance completed', 'success');
|
|
|
|
return { success: true };
|
|
} catch (error) {
|
|
await execution?.addLog(`Database maintenance error: ${getErrorMessage(error)}`, 'error');
|
|
throw error;
|
|
}
|
|
},
|
|
});
|
|
|
|
taskManager.registerTask('db-maintenance', dbMaintenance, {
|
|
description: 'Optimize database performance',
|
|
category: 'maintenance',
|
|
schedule: '0 4 * * 0', // Weekly on Sunday at 4 AM
|
|
enabled: true,
|
|
});
|
|
|
|
// Security Scan
|
|
const securityScan = new plugins.taskbuffer.Task({
|
|
name: 'security-scan',
|
|
taskFunction: async () => {
|
|
const execution = taskManager.getCurrentExecution('security-scan');
|
|
|
|
try {
|
|
await execution?.addLog('Starting security scan...', 'info');
|
|
if (execution && (taskManager.isCancellationRequested(execution.id) || execution.data.status === 'cancelled')) {
|
|
await execution.addLog('Cancellation requested. Aborting security scan...', 'warning');
|
|
return;
|
|
}
|
|
|
|
const vulnerabilities: Array<{ type: string; severity: string; image: string; version: string }> = [];
|
|
|
|
// Check for exposed ports
|
|
await execution?.addLog('Checking for exposed ports...', 'info');
|
|
// TODO: Actual port scanning logic
|
|
|
|
// Check for outdated images
|
|
await execution?.addLog('Checking for outdated images...', 'info');
|
|
const images = await taskManager.cloudlyRef.imageManager.CImage.getInstances({});
|
|
|
|
for (const image of images) {
|
|
if (execution && (taskManager.isCancellationRequested(execution.id) || execution.data.status === 'cancelled')) {
|
|
await execution.addLog('Cancellation requested. Stopping loop...', 'warning');
|
|
break;
|
|
}
|
|
// TODO: Check if image is outdated
|
|
const isOutdated = Math.random() > 0.7; // 30% outdated for demo
|
|
|
|
if (isOutdated) {
|
|
vulnerabilities.push({
|
|
type: 'outdated-image',
|
|
severity: 'medium',
|
|
image: image.data.name,
|
|
version: image.data.versions[0]?.versionString || 'unknown',
|
|
});
|
|
}
|
|
}
|
|
|
|
// Check for weak passwords
|
|
await execution?.addLog('Checking for weak configurations...', 'info');
|
|
// TODO: Configuration checks
|
|
|
|
await execution?.setMetric('vulnerabilitiesFound', vulnerabilities.length);
|
|
await execution?.setMetric('vulnerabilities', vulnerabilities);
|
|
|
|
const severity = vulnerabilities.length > 0 ? 'warning' : 'success';
|
|
await execution?.addLog(
|
|
`Security scan completed: ${vulnerabilities.length} vulnerabilities found`,
|
|
severity as any
|
|
);
|
|
|
|
return { vulnerabilities };
|
|
} catch (error) {
|
|
await execution?.addLog(`Security scan error: ${getErrorMessage(error)}`, 'error');
|
|
throw error;
|
|
}
|
|
},
|
|
});
|
|
|
|
taskManager.registerTask('security-scan', securityScan, {
|
|
description: 'Run security checks on services',
|
|
category: 'security',
|
|
schedule: '0 1 * * *', // Daily at 1 AM
|
|
enabled: true,
|
|
});
|
|
|
|
// Docker Cleanup
|
|
const dockerCleanup = new plugins.taskbuffer.Task({
|
|
name: 'docker-cleanup',
|
|
taskFunction: async () => {
|
|
const execution = taskManager.getCurrentExecution('docker-cleanup');
|
|
|
|
try {
|
|
await execution?.addLog('Starting Docker cleanup...', 'info');
|
|
|
|
// TODO: Implement actual Docker cleanup
|
|
await execution?.addLog('Removing stopped containers...', 'info');
|
|
const removedContainers = 0; // Placeholder
|
|
|
|
await execution?.addLog('Removing unused images...', 'info');
|
|
const removedImages = 0; // Placeholder
|
|
|
|
await execution?.addLog('Removing unused volumes...', 'info');
|
|
const removedVolumes = 0; // Placeholder
|
|
|
|
await execution?.addLog('Removing unused networks...', 'info');
|
|
const removedNetworks = 0; // Placeholder
|
|
|
|
await execution?.setMetric('removedContainers', removedContainers);
|
|
await execution?.setMetric('removedImages', removedImages);
|
|
await execution?.setMetric('removedVolumes', removedVolumes);
|
|
await execution?.setMetric('removedNetworks', removedNetworks);
|
|
|
|
await execution?.addLog(
|
|
`Docker cleanup completed: ${removedContainers} containers, ${removedImages} images, ${removedVolumes} volumes, ${removedNetworks} networks removed`,
|
|
'success'
|
|
);
|
|
|
|
return {
|
|
containers: removedContainers,
|
|
images: removedImages,
|
|
volumes: removedVolumes,
|
|
networks: removedNetworks,
|
|
};
|
|
} catch (error) {
|
|
await execution?.addLog(`Docker cleanup error: ${getErrorMessage(error)}`, 'error');
|
|
throw error;
|
|
}
|
|
},
|
|
});
|
|
|
|
taskManager.registerTask('docker-cleanup', dockerCleanup, {
|
|
description: 'Remove unused Docker images and containers',
|
|
category: 'cleanup',
|
|
schedule: '0 5 * * *', // Daily at 5 AM
|
|
enabled: true,
|
|
});
|
|
|
|
logger.log('info', 'Predefined tasks registered successfully');
|
|
}
|