diff --git a/api/package.json b/api/package.json index 8804223a1..25ca3c9a7 100644 --- a/api/package.json +++ b/api/package.json @@ -94,7 +94,7 @@ "command-exists": "1.2.9", "convert": "5.12.0", "cookie": "1.0.2", - "cron": "4.3.1", + "cron": "4.3.0", "cross-fetch": "4.1.0", "diff": "8.0.2", "dockerode": "4.0.7", @@ -192,6 +192,7 @@ "@types/wtfnode": "0.7.3", "@vitest/coverage-v8": "3.2.4", "@vitest/ui": "3.2.4", + "commit-and-tag-version": "9.6.0", "cz-conventional-changelog": "3.3.0", "eslint": "9.30.1", "eslint-plugin-import": "2.32.0", @@ -203,7 +204,6 @@ "nodemon": "3.1.10", "prettier": "3.6.2", "rollup-plugin-node-externals": "8.0.1", - "commit-and-tag-version": "9.6.0", "tsx": "4.20.3", "type-fest": "4.41.0", "typescript": "5.8.3", @@ -225,7 +225,8 @@ "nest-authz": { "@nestjs/common": "$@nestjs/common", "@nestjs/core": "$@nestjs/core" - } + }, + "cron": "4.3.1" }, "private": true, "packageManager": "pnpm@10.13.1" diff --git a/api/src/unraid-api/graph/resolvers/backup/backup-config.service.ts b/api/src/unraid-api/graph/resolvers/backup/backup-config.service.ts index 43a810494..55b012036 100644 --- a/api/src/unraid-api/graph/resolvers/backup/backup-config.service.ts +++ b/api/src/unraid-api/graph/resolvers/backup/backup-config.service.ts @@ -13,6 +13,7 @@ import { CreateBackupJobConfigInput, UpdateBackupJobConfigInput, } from '@app/unraid-api/graph/resolvers/backup/backup.model.js'; +import { getBackupJobGroupId } from '@app/unraid-api/graph/resolvers/backup/backup.utils.js'; import { DestinationConfigInput, DestinationType, @@ -520,7 +521,7 @@ export class BackupConfigService implements OnModuleInit { 'UTC' ); - this.schedulerRegistry.addCronJob(`${JOB_GROUP_PREFIX}${config.id}`, job); + this.schedulerRegistry.addCronJob(getBackupJobGroupId(config.id), job); job.start(); this.logger.log(`Scheduled backup job: ${config.name} with schedule: ${config.schedule}`); } catch (error) { @@ -530,7 +531,7 @@ export class BackupConfigService implements OnModuleInit { private unscheduleJob(id: string): void { try { - const jobName = `${JOB_GROUP_PREFIX}${id}`; + const jobName = getBackupJobGroupId(id); if (this.schedulerRegistry.doesExist('cron', jobName)) { this.schedulerRegistry.deleteCronJob(jobName); this.logger.log(`Unscheduled backup job: ${id}`); diff --git a/api/src/unraid-api/graph/resolvers/backup/orchestration/backup-orchestration.service.ts b/api/src/unraid-api/graph/resolvers/backup/orchestration/backup-orchestration.service.ts index 00e2a617b..01bee7047 100644 --- a/api/src/unraid-api/graph/resolvers/backup/orchestration/backup-orchestration.service.ts +++ b/api/src/unraid-api/graph/resolvers/backup/orchestration/backup-orchestration.service.ts @@ -81,8 +81,7 @@ export class BackupOrchestrationService { sourceProcessor, destinationProcessor, jobConfig, - internalJobId, - configId // Pass configId for handleJobCompletion + internalJobId ); } else { await this.executeRegularBackup( @@ -201,7 +200,7 @@ export class BackupOrchestrationService { this.emitJobStatus(internalJobId, { status: BackupJobStatus.FAILED, error: errorMsg }); // Call handleJobCompletion before throwing await this.backupConfigService.handleJobCompletion( - configId, + jobConfig.id, BackupJobStatus.FAILED, internalJobId ); @@ -218,7 +217,7 @@ export class BackupOrchestrationService { }); // Call handleJobCompletion on success await this.backupConfigService.handleJobCompletion( - configId, + jobConfig.id, BackupJobStatus.COMPLETED, internalJobId ); @@ -251,7 +250,7 @@ export class BackupOrchestrationService { }); // Call handleJobCompletion on failure await this.backupConfigService.handleJobCompletion( - configId, + jobConfig.id, BackupJobStatus.FAILED, internalJobId ); diff --git a/api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts b/api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts index a59a1e2b9..157ab5f50 100644 --- a/api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts +++ b/api/src/unraid-api/graph/resolvers/rclone/rclone-api.service.ts @@ -11,6 +11,11 @@ import got, { HTTPError } from 'got'; import pRetry from 'p-retry'; import { sanitizeParams } from '@app/core/log.js'; +import { + getConfigIdFromGroupId, + isBackupJobGroup, +} from '@app/unraid-api/graph/resolvers/backup/backup.utils.js'; +import { BackupJobStatus } from '@app/unraid-api/graph/resolvers/backup/orchestration/backup-job-status.model.js'; import { RCloneStatusService } from '@app/unraid-api/graph/resolvers/rclone/rclone-status.service.js'; import { CreateRCloneRemoteDto, @@ -72,7 +77,7 @@ interface JobOperationResult { @Injectable() export class RCloneApiService implements OnModuleInit, OnModuleDestroy { - private isInitialized: boolean = false; + private initialized: boolean = false; private readonly logger = new Logger(RCloneApiService.name); private rcloneSocketPath: string = ''; private rcloneBaseUrl: string = ''; @@ -86,12 +91,51 @@ export class RCloneApiService implements OnModuleInit, OnModuleDestroy { constructor(private readonly statusService: RCloneStatusService) {} + get isInitialized(): boolean { + return this.initialized; + } + async onModuleInit(): Promise { - try { - await this.initializeRCloneService(); - } catch (error: unknown) { - this.logger.error(`Error initializing RCloneApiService: ${error}`); - this.isInitialized = false; + // Check if rclone binary is available first + const isBinaryAvailable = await this.checkRcloneBinaryExists(); + if (!isBinaryAvailable) { + this.logger.warn('RClone binary not found on system, skipping initialization'); + this.initialized = false; + return; + } + + const { getters } = await import('@app/store/index.js'); + // Check if Rclone Socket is running, if not, start it. + this.rcloneSocketPath = getters.paths()['rclone-socket']; + const logFilePath = join(getters.paths()['log-base'], 'rclone-unraid-api.log'); + this.logger.log(`RClone socket path: ${this.rcloneSocketPath}`); + this.logger.log(`RClone log file path: ${logFilePath}`); + + // Format the base URL for Unix socket + this.rcloneBaseUrl = `http://unix:${this.rcloneSocketPath}:`; + + // Check if the RClone socket exists, if not, create it. + const socketExists = await this.checkRcloneSocketExists(this.rcloneSocketPath); + + if (socketExists) { + const isRunning = await this.checkRcloneSocketRunning(); + if (isRunning) { + this.initialized = true; + return; + } else { + this.logger.warn( + 'RClone socket is not running but socket exists, removing socket before starting...' + ); + await rm(this.rcloneSocketPath, { force: true }); + } + + this.logger.warn('RClone socket is not running, starting it...'); + this.initialized = await this.startRcloneSocket(this.rcloneSocketPath, logFilePath); + return; + } else { + this.logger.warn('RClone socket does not exist, creating it...'); + this.initialized = await this.startRcloneSocket(this.rcloneSocketPath, logFilePath); + return; } } @@ -114,7 +158,7 @@ export class RCloneApiService implements OnModuleInit, OnModuleDestroy { await this.stopRcloneSocket(); this.logger.warn('Proceeding to start new RClone socket...'); - this.isInitialized = await this.startRcloneSocket(this.rcloneSocketPath, logFilePath); + this.initialized = await this.startRcloneSocket(this.rcloneSocketPath, logFilePath); } private async startRcloneSocket(socketPath: string, logFilePath: string): Promise { @@ -200,7 +244,7 @@ export class RCloneApiService implements OnModuleInit, OnModuleDestroy { private cleanupFailedProcess(): void { this.rcloneProcess = null; - this.isInitialized = false; + this.initialized = false; } private async waitForSocketReady(): Promise { @@ -349,9 +393,7 @@ export class RCloneApiService implements OnModuleInit, OnModuleDestroy { this.logger.log(`Starting backup: ${input.srcPath} → ${input.dstPath}`); - const group = input.configId - ? getBackupJobGroupId(input.configId) - : BACKUP_JOB_GROUP_PREFIX + 'manual'; + const group = input.configId ? getConfigIdFromGroupId(input.configId) : 'manual'; const params = { srcFs: input.srcPath, diff --git a/api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts b/api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts index 80c33f991..8e2743672 100644 --- a/api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts +++ b/api/src/unraid-api/graph/resolvers/rclone/rclone.service.ts @@ -48,7 +48,7 @@ export class RCloneService { */ async onModuleInit(): Promise { try { - if (!this.rcloneApiService.initialized) { + if (!this.rcloneApiService.isInitialized) { this.logger.warn( 'RClone API service is not initialized, skipping provider info loading' ); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 01d345efd..e74a4a201 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -157,8 +157,8 @@ importers: specifier: 1.0.2 version: 1.0.2 cron: - specifier: 4.3.1 - version: 4.3.1 + specifier: 4.3.0 + version: 4.3.0 cross-fetch: specifier: 4.1.0 version: 4.1.0 @@ -6509,10 +6509,6 @@ packages: resolution: {integrity: sha512-ciiYNLfSlF9MrDqnbMdRWFiA6oizSF7kA1osPP9lRzNu0Uu+AWog1UKy7SkckiDY2irrNjeO6qLyKnXC8oxmrw==} engines: {node: '>=18.x'} - cron@4.3.1: - resolution: {integrity: sha512-7x7DoEOxV11t3OPWWMjj1xrL1PGkTV5RV+/54IJTZD7gStiaMploY43EkeBSkDZTLRbUwk+OISbQ0TR133oXyA==} - engines: {node: '>=18.x'} - croner@4.1.97: resolution: {integrity: sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ==} @@ -19428,11 +19424,6 @@ snapshots: '@types/luxon': 3.6.2 luxon: 3.6.1 - cron@4.3.1: - dependencies: - '@types/luxon': 3.6.2 - luxon: 3.6.1 - croner@4.1.97: {} croner@9.1.0: {}