From fe2596dc0e389c0496a384cc1893fddd4742ed37 Mon Sep 17 00:00:00 2001 From: JPVenson Date: Mon, 19 May 2025 03:39:04 +0300 Subject: Add Full system backup feature (#13945) --- Jellyfin.Api/Controllers/BackupController.cs | 127 +++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 Jellyfin.Api/Controllers/BackupController.cs (limited to 'Jellyfin.Api/Controllers/BackupController.cs') diff --git a/Jellyfin.Api/Controllers/BackupController.cs b/Jellyfin.Api/Controllers/BackupController.cs new file mode 100644 index 000000000..aa908ee30 --- /dev/null +++ b/Jellyfin.Api/Controllers/BackupController.cs @@ -0,0 +1,127 @@ +using System.IO; +using System.Threading.Tasks; +using Jellyfin.Server.Implementations.SystemBackupService; +using MediaBrowser.Common.Api; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.SystemBackupService; +using Microsoft.AspNetCore.Authentication.OAuth.Claims; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Jellyfin.Api.Controllers; + +/// +/// The backup controller. +/// +[Authorize(Policy = Policies.RequiresElevation)] +public class BackupController : BaseJellyfinApiController +{ + private readonly IBackupService _backupService; + private readonly IApplicationPaths _applicationPaths; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + public BackupController(IBackupService backupService, IApplicationPaths applicationPaths) + { + _backupService = backupService; + _applicationPaths = applicationPaths; + } + + /// + /// Creates a new Backup. + /// + /// The backup options. + /// Backup created. + /// User does not have permission to retrieve information. + /// The created backup manifest. + [HttpPost("Create")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task> CreateBackup([FromBody] BackupOptionsDto backupOptions) + { + return Ok(await _backupService.CreateBackupAsync(backupOptions ?? new()).ConfigureAwait(false)); + } + + /// + /// Restores to a backup by restarting the server and applying the backup. + /// + /// The data to start a restore process. + /// Backup restore started. + /// User does not have permission to retrieve information. + /// No-Content. + [HttpPost("Restore")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public IActionResult StartRestoreBackup([FromBody, BindRequired] BackupRestoreRequestDto archiveRestoreDto) + { + var archivePath = SanitizePath(archiveRestoreDto.ArchiveFileName); + if (!System.IO.File.Exists(archivePath)) + { + return NotFound(); + } + + _backupService.ScheduleRestoreAndRestartServer(archivePath); + return NoContent(); + } + + /// + /// Gets a list of all currently present backups in the backup directory. + /// + /// Backups available. + /// User does not have permission to retrieve information. + /// The list of backups. + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task> ListBackups() + { + return Ok(await _backupService.EnumerateBackups().ConfigureAwait(false)); + } + + /// + /// Gets the descriptor from an existing archive is present. + /// + /// The data to start a restore process. + /// Backup archive manifest. + /// Not a valid jellyfin Archive. + /// Not a valid path. + /// User does not have permission to retrieve information. + /// The backup manifest. + [HttpGet("Manifest")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public async Task> GetBackup([BindRequired] string path) + { + var backupPath = SanitizePath(path); + + if (!System.IO.File.Exists(backupPath)) + { + return NotFound(); + } + + var manifest = await _backupService.GetBackupManifest(backupPath).ConfigureAwait(false); + if (manifest is null) + { + return NoContent(); + } + + return Ok(manifest); + } + + [NonAction] + private string SanitizePath(string path) + { + // sanitize path + var archiveRestorePath = Path.GetFileName(Path.GetFullPath(path)); + var archivePath = Path.Combine(_applicationPaths.BackupPath, archiveRestorePath); + return archivePath; + } +} -- cgit v1.2.3