From 4cc4d167d5d8f9029eb8c04ffe85ed863343315a Mon Sep 17 00:00:00 2001 From: Luck16048 Date: Fri, 23 Jan 2026 02:19:33 +0100 Subject: [PATCH 1/8] Test FTP backup for taskbeat.pl --- FtpBackupRunner.cs | 2 +- Program.cs | 27 +++++++++++++++++++++++++++ appsettings.json | 18 +++++++++++------- 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/FtpBackupRunner.cs b/FtpBackupRunner.cs index de21989..f6463c2 100644 --- a/FtpBackupRunner.cs +++ b/FtpBackupRunner.cs @@ -165,7 +165,7 @@ private void LogResults(string jobName, List results) } } - private static void CopyDirectory( + public static void CopyDirectory( string sourceDir, string targetDir, CancellationToken cancellationToken) diff --git a/Program.cs b/Program.cs index b9a8576..e8f8953 100644 --- a/Program.cs +++ b/Program.cs @@ -2,6 +2,15 @@ using Microsoft.Extensions.Logging.EventLog; var builder = Host.CreateApplicationBuilder(args); +var backupOptions = builder.Configuration.GetSection("BackupOptions").Get(); + + if (!backupOptions.Backups.Any()) + { + Console.WriteLine("Nie znaleziono backupów w konfiguracji!"); + return; + } + +var backupJob = backupOptions.Backups.First(); builder.Services.Configure( builder.Configuration.GetSection(BackupOptions.SectionName)); @@ -23,6 +32,7 @@ LogName = "Application", SourceName = "BackupService" }); + builder.Logging.AddFileLogger( builder.Configuration.GetSection(FileLoggerOptions.SectionName)); @@ -34,4 +44,21 @@ }); var host = builder.Build(); + +var logger = host.Services.GetRequiredService>(); +// FtpBackupRunner: kopiuje pliki z serwera FTP na lokalny dysk +var ftpRunner = new FtpBackupRunner(logger); + +try +{ + Console.WriteLine("I'm starting a test of downloading files from FTP"); + + await ftpRunner.RunJobAsync(backupJob, backupOptions, CancellationToken.None); + Console.WriteLine("FTP test completed! Files should be in C:\\Remotebeat.test"); +} +catch (Exception ex) +{ + Console.WriteLine($"An error occurred during the test: {ex.Message}"); +} + host.Run(); diff --git a/appsettings.json b/appsettings.json index 8c8cd6a..a92546a 100644 --- a/appsettings.json +++ b/appsettings.json @@ -10,6 +10,10 @@ "MinimumLevel": "Information" }, "BackupOptions": { + "Host": "taskbeat.pl", + "Username": "admin@taskbeat.pl", + "Password": "Lq3HsTLJHUvg3PfpvQv6", + "LocalPath": "C:\\Remotebeat.test", "RunAt": "02:00", "HistoryCopies": 5, "DefaultTimeoutMinutes": 60, @@ -17,16 +21,16 @@ "HistorySubdirectoryName": "_history", "Backups": [ { - "Name": "SampleBackup", - "Host": "ftp.example.com", + "Name": "Taskbeat.pl", + "Host": "taskbeat.pl", "Port": 21, - "Username": "user", - "Password": "password", - "RemotePath": "/", - "LocalPath": "D:\\Backups\\Sample", + "Username": "admin@taskbeat.pl", + "Password": "Lq3HsTLJHUvg3PfpvQv6", + "RemotePath": "/public_html/taskbeat.pl/images", + "LocalPath": "C:\\Remotebeat.test", "Encryption": "Explicit", "Passive": true, - "AllowInvalidCertificate": false, + "AllowInvalidCertificate": true, "TimeoutMinutes": 60, "HistoryCopies": 5 } From 17342419c5aa35ca1d9b805769b7c3f096f6754d Mon Sep 17 00:00:00 2001 From: Luck16048 Date: Sat, 24 Jan 2026 08:08:08 +0100 Subject: [PATCH 2/8] New Commit --- Program.cs | 2 +- appsettings.json | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Program.cs b/Program.cs index e8f8953..828edca 100644 --- a/Program.cs +++ b/Program.cs @@ -54,7 +54,7 @@ Console.WriteLine("I'm starting a test of downloading files from FTP"); await ftpRunner.RunJobAsync(backupJob, backupOptions, CancellationToken.None); - Console.WriteLine("FTP test completed! Files should be in C:\\Remotebeat.test"); + Console.WriteLine("FTP test completed! Files should be in /"); } catch (Exception ex) { diff --git a/appsettings.json b/appsettings.json index a92546a..108cef0 100644 --- a/appsettings.json +++ b/appsettings.json @@ -10,10 +10,10 @@ "MinimumLevel": "Information" }, "BackupOptions": { - "Host": "taskbeat.pl", - "Username": "admin@taskbeat.pl", - "Password": "Lq3HsTLJHUvg3PfpvQv6", - "LocalPath": "C:\\Remotebeat.test", + "Host": "FTP_HOST", + "Username": "FTP_USERNAME", + "Password": "FTP_PASSWORD", + "LocalPath": "/", "RunAt": "02:00", "HistoryCopies": 5, "DefaultTimeoutMinutes": 60, @@ -22,12 +22,12 @@ "Backups": [ { "Name": "Taskbeat.pl", - "Host": "taskbeat.pl", + "Host": "FTP_HOST", "Port": 21, - "Username": "admin@taskbeat.pl", - "Password": "Lq3HsTLJHUvg3PfpvQv6", - "RemotePath": "/public_html/taskbeat.pl/images", - "LocalPath": "C:\\Remotebeat.test", + "Username": "FTP_USERNAME", + "Password": "FTP_PASSWORD", + "RemotePath": "/", + "LocalPath": "/", "Encryption": "Explicit", "Passive": true, "AllowInvalidCertificate": true, From 60e5e268ec61ef368e5bae43b03f87b7ff8e2c39 Mon Sep 17 00:00:00 2001 From: Luck16048 Date: Sat, 24 Jan 2026 09:07:38 +0100 Subject: [PATCH 3/8] New commit for FTP test --- appsettings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appsettings.json b/appsettings.json index 108cef0..4fb3530 100644 --- a/appsettings.json +++ b/appsettings.json @@ -21,7 +21,7 @@ "HistorySubdirectoryName": "_history", "Backups": [ { - "Name": "Taskbeat.pl", + "Name": "EXAMPLE_NAME", "Host": "FTP_HOST", "Port": 21, "Username": "FTP_USERNAME", From 82ae5cef2d97429ed4e378e570c0cbbd656f403b Mon Sep 17 00:00:00 2001 From: Luck16048 Date: Fri, 30 Jan 2026 13:16:10 +0100 Subject: [PATCH 4/8] Added zip backup per host with retention --- BackupJobOptions.cs | 1 + FtpBackupRunner.cs | 23 +++++++++++++++++++++++ Program.cs | 4 ++-- appsettings.json | 5 +++-- 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/BackupJobOptions.cs b/BackupJobOptions.cs index 3299cda..717522d 100644 --- a/BackupJobOptions.cs +++ b/BackupJobOptions.cs @@ -14,4 +14,5 @@ public class BackupJobOptions public bool AllowInvalidCertificate { get; set; } public int? TimeoutMinutes { get; set; } public int? HistoryCopies { get; set; } + public int RetentionDays { get; set; } = 7; } diff --git a/FtpBackupRunner.cs b/FtpBackupRunner.cs index f6463c2..ecfd237 100644 --- a/FtpBackupRunner.cs +++ b/FtpBackupRunner.cs @@ -1,5 +1,6 @@ using FluentFTP; using System.Globalization; +using System.IO.Compression; namespace BackupService; @@ -104,6 +105,28 @@ public async Task RunJobAsync( CopyDirectory(currentRoot, snapshotPath, cancellationToken); CleanupHistory(historyRoot, job, options); + + string currentDir = Path.Combine(job.LocalPath, "current"); + Directory.CreateDirectory(currentDir); + + string archiveDir = Path.Combine(job.LocalPath, "archives", job.Name); + Directory.CreateDirectory(archiveDir); + + string zipPath = Path.Combine(archiveDir, DateTime.Now.ToString("yyyy-MM-dd") + ".zip"); + if (File.Exists(zipPath)) + { + File.Delete(zipPath); + } + ZipFile.CreateFromDirectory(currentDir, zipPath, CompressionLevel.Optimal, includeBaseDirectory: false); + + foreach (var file in Directory.GetFiles(archiveDir, "*.zip")) + { + var creationDate = File.GetCreationTime(file); + if ((DateTime.Now - creationDate).TotalDays > job.RetentionDays) + File.Delete(file); + } + + Directory.Delete(currentDir, recursive: true); } private static bool IsLocalDrivePath(string path) diff --git a/Program.cs b/Program.cs index 828edca..f9d7fd1 100644 --- a/Program.cs +++ b/Program.cs @@ -6,7 +6,7 @@ if (!backupOptions.Backups.Any()) { - Console.WriteLine("Nie znaleziono backupów w konfiguracji!"); + Console.WriteLine("No backups found in configuration!fr55"); return; } @@ -46,7 +46,7 @@ var host = builder.Build(); var logger = host.Services.GetRequiredService>(); -// FtpBackupRunner: kopiuje pliki z serwera FTP na lokalny dysk + var ftpRunner = new FtpBackupRunner(logger); try diff --git a/appsettings.json b/appsettings.json index 4fb3530..b2918d9 100644 --- a/appsettings.json +++ b/appsettings.json @@ -12,11 +12,12 @@ "BackupOptions": { "Host": "FTP_HOST", "Username": "FTP_USERNAME", - "Password": "FTP_PASSWORD", + "Password": "FTP_PASSPORT", "LocalPath": "/", "RunAt": "02:00", "HistoryCopies": 5, "DefaultTimeoutMinutes": 60, + "RetentionDays": 7, "CurrentSubdirectoryName": "current", "HistorySubdirectoryName": "_history", "Backups": [ @@ -25,7 +26,7 @@ "Host": "FTP_HOST", "Port": 21, "Username": "FTP_USERNAME", - "Password": "FTP_PASSWORD", + "Password": "FTP_PASSPORT", "RemotePath": "/", "LocalPath": "/", "Encryption": "Explicit", From f88ef6544b699c8d25d17e84279a4a0b53d48318 Mon Sep 17 00:00:00 2001 From: Luck16048 Date: Fri, 30 Jan 2026 13:39:35 +0100 Subject: [PATCH 5/8] Fix accidental hash name --- Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Program.cs b/Program.cs index f9d7fd1..ad772c0 100644 --- a/Program.cs +++ b/Program.cs @@ -6,7 +6,7 @@ if (!backupOptions.Backups.Any()) { - Console.WriteLine("No backups found in configuration!fr55"); + Console.WriteLine("No backups found in configuration!"); return; } From cc8f370535a5f558531ea33b471ca29126679e17 Mon Sep 17 00:00:00 2001 From: Luck16048 Date: Sat, 31 Jan 2026 14:55:41 +0100 Subject: [PATCH 6/8] =?UTF-8?q?Zmiana=20dzia=C5=82ania=20konfiguracji=20ti?= =?UTF-8?q?meout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BackupJobOptions.cs | 2 + FtpBackupRunner.cs | 267 ++++++++++++++++++++++++++------------------ appsettings.json | 18 +-- 3 files changed, 170 insertions(+), 117 deletions(-) diff --git a/BackupJobOptions.cs b/BackupJobOptions.cs index 717522d..c41eb43 100644 --- a/BackupJobOptions.cs +++ b/BackupJobOptions.cs @@ -15,4 +15,6 @@ public class BackupJobOptions public int? TimeoutMinutes { get; set; } public int? HistoryCopies { get; set; } public int RetentionDays { get; set; } = 7; + public int OperationTimeoutMinutes { get; set; } = 10; + public int CompletionTimeoutMinutes { get; set; } = 180; } diff --git a/FtpBackupRunner.cs b/FtpBackupRunner.cs index ecfd237..f6072bf 100644 --- a/FtpBackupRunner.cs +++ b/FtpBackupRunner.cs @@ -1,6 +1,8 @@ using FluentFTP; using System.Globalization; using System.IO.Compression; +using System.Net; + namespace BackupService; @@ -11,125 +13,172 @@ public async Task RunJobAsync( BackupOptions options, CancellationToken cancellationToken) { - if (string.IsNullOrWhiteSpace(job.Host)) - { - logger.LogError("Backup '{name}' is missing Host.", job.Name); - return; - } + using var completionCts = + new CancellationTokenSource(TimeSpan.FromMinutes(job.CompletionTimeoutMinutes)); - if (string.IsNullOrWhiteSpace(job.LocalPath)) - { - logger.LogError("Backup '{name}' is missing LocalPath.", job.Name); - return; - } + using var linkedCts = + CancellationTokenSource.CreateLinkedTokenSource( + cancellationToken, + completionCts.Token); - if (!IsLocalDrivePath(job.LocalPath)) - { - logger.LogError( - "Backup '{name}' LocalPath '{path}' is not a local drive path.", - job.Name, - job.LocalPath); - return; - } + var completionToken = linkedCts.Token; - var currentRoot = Path.Combine( - job.LocalPath, - options.CurrentSubdirectoryName); - var historyRoot = Path.Combine( - job.LocalPath, - options.HistorySubdirectoryName); - - Directory.CreateDirectory(currentRoot); - Directory.CreateDirectory(historyRoot); - - using var client = new FtpClient(job.Host, job.Username, job.Password, job.Port); - - client.Config.EncryptionMode = ParseEncryptionMode(job.Encryption); - client.Config.DataConnectionType = job.Passive - ? FtpDataConnectionType.PASV - : FtpDataConnectionType.PORT; - client.Config.DataConnectionEncryption = true; - client.Config.ConnectTimeout = 15000; - client.Config.ReadTimeout = 15000; - client.Config.DataConnectionConnectTimeout = 15000; - client.Config.DataConnectionReadTimeout = 15000; - client.Config.ValidateAnyCertificate = job.AllowInvalidCertificate; - - if (!job.AllowInvalidCertificate) + try { - client.ValidateCertificate += (_, e) => + + if (string.IsNullOrWhiteSpace(job.Host)) { - if (e.PolicyErrors != System.Net.Security.SslPolicyErrors.None) + logger.LogError("Backup '{name}' is missing Host.", job.Name); + return; + } + + if (string.IsNullOrWhiteSpace(job.LocalPath)) + { + logger.LogError("Backup '{name}' is missing LocalPath.", job.Name); + return; + } + + if (!IsLocalDrivePath(job.LocalPath)) + { + logger.LogError( + "Backup '{name}' LocalPath '{path}' is not a local drive path.", + job.Name, + job.LocalPath); + return; + } + + var currentRoot = Path.Combine( + job.LocalPath, + options.CurrentSubdirectoryName); + var historyRoot = Path.Combine( + job.LocalPath, + options.HistorySubdirectoryName); + + Directory.CreateDirectory(currentRoot); + Directory.CreateDirectory(historyRoot); + + using var client = new FtpClient(job.Host, job.Username, job.Password, job.Port); + + client.Config.EncryptionMode = ParseEncryptionMode(job.Encryption); + client.Config.DataConnectionType = job.Passive + ? FtpDataConnectionType.PASV + : FtpDataConnectionType.PORT; + client.Config.DataConnectionEncryption = true; + client.Config.ConnectTimeout = 15000; + client.Config.ReadTimeout = 15000; + client.Config.DataConnectionConnectTimeout = 15000; + client.Config.DataConnectionReadTimeout = 15000; + client.Config.ValidateAnyCertificate = job.AllowInvalidCertificate; + + if (!job.AllowInvalidCertificate) + { + client.ValidateCertificate += (_, e) => { - logger.LogError( - "Certificate validation failed for backup '{name}': {errors}", - job.Name, - e.PolicyErrors); - } - }; - } + if (e.PolicyErrors != System.Net.Security.SslPolicyErrors.None) + { + logger.LogError( + "Certificate validation failed for backup '{name}': {errors}", + job.Name, + e.PolicyErrors); + } + }; + } + + var remotePath = string.IsNullOrWhiteSpace(job.RemotePath) + ? "/" + : job.RemotePath; - var remotePath = string.IsNullOrWhiteSpace(job.RemotePath) - ? "/" - : job.RemotePath; - - logger.LogInformation( - "Connecting to {host}:{port} for backup '{name}'.", - job.Host, - job.Port, - job.Name); - - client.Connect(); - logger.LogInformation( - "Connected to {host}. Starting mirror of {remotePath}.", - job.Host, - remotePath); - - var results = client.DownloadDirectory( - currentRoot, - remotePath, - FtpFolderSyncMode.Mirror, - FtpLocalExists.Overwrite, - FtpVerify.None); - - LogResults(job.Name, results); - - var snapshotName = DateTimeOffset.Now - .ToString("yyyyMMdd_HHmmss", CultureInfo.InvariantCulture); - var snapshotPath = Path.Combine(historyRoot, snapshotName); - - logger.LogInformation( - "Creating history snapshot for backup '{name}' at {path}.", - job.Name, - snapshotPath); - - CopyDirectory(currentRoot, snapshotPath, cancellationToken); - CleanupHistory(historyRoot, job, options); - - string currentDir = Path.Combine(job.LocalPath, "current"); - Directory.CreateDirectory(currentDir); - - string archiveDir = Path.Combine(job.LocalPath, "archives", job.Name); - Directory.CreateDirectory(archiveDir); - - string zipPath = Path.Combine(archiveDir, DateTime.Now.ToString("yyyy-MM-dd") + ".zip"); - if (File.Exists(zipPath)) + logger.LogInformation( + "Connecting to {host}:{port} for backup '{name}'.", + job.Host, + job.Port, + job.Name); + + client.Connect(); + logger.LogInformation( + "Connected to {host}. Starting mirror of {remotePath}.", + job.Host, + remotePath); + + var results = client.DownloadDirectory( + currentRoot, + remotePath, + FtpFolderSyncMode.Mirror, + FtpLocalExists.Overwrite, + FtpVerify.None); + + LogResults(job.Name, results); + + var snapshotName = DateTimeOffset.Now + .ToString("yyyyMMdd_HHmmss", CultureInfo.InvariantCulture); + var snapshotPath = Path.Combine(historyRoot, snapshotName); + + logger.LogInformation( + "Creating history snapshot for backup '{name}' at {path}.", + job.Name, + snapshotPath); + + + CopyDirectory(currentRoot, snapshotPath, completionToken); + CleanupHistory(historyRoot, job, options); + + string currentDir = Path.Combine(job.LocalPath, "current"); + Directory.CreateDirectory(currentDir); + + CopyDirectory(snapshotPath, currentDir, completionToken); + + string archiveDir = Path.Combine(job.LocalPath, "archives", job.Name); + Directory.CreateDirectory(archiveDir); + + string zipPath = Path.Combine(archiveDir, DateTime.Now.ToString("yyyy-MM-dd") + ".zip"); + + if (File.Exists(zipPath)) + { + File.Delete(zipPath); + } + + ZipFile.CreateFromDirectory(currentDir, zipPath, CompressionLevel.Optimal, includeBaseDirectory: false); + + foreach (var file in Directory.GetFiles(archiveDir, "*.zip")) + { + var creationDate = File.GetCreationTime(file); + if ((DateTime.Now - creationDate).TotalDays > job.RetentionDays) + File.Delete(file); + } + + foreach (var file in Directory.GetFiles(currentDir)) + File.Delete(file); + + foreach (var dir in Directory.GetDirectories(currentDir)) + Directory.Delete(dir, recursive: true); + + using var operationCts = new CancellationTokenSource(TimeSpan.FromMinutes(job.OperationTimeoutMinutes)); + using var opLinkedCts = CancellationTokenSource.CreateLinkedTokenSource(completionToken, operationCts.Token); + + string host = job.Host; + string username = job.Username; + string password = job.Password; + var ftpClient = new FtpClient(); + ftpClient.Host = host; + ftpClient.Credentials = new NetworkCredential(username, password); + ftpClient.Connect(); + + string localPath = Path.Combine(job.LocalPath, "current"); + ftpClient.DownloadDirectory(remotePath, localPath); + ftpClient.Disconnect(); + } + catch (OperationCanceledException) { - File.Delete(zipPath); + logger.LogWarning("Backup '{name}' cancelled.", job.Name); } - ZipFile.CreateFromDirectory(currentDir, zipPath, CompressionLevel.Optimal, includeBaseDirectory: false); - - foreach (var file in Directory.GetFiles(archiveDir, "*.zip")) + catch (Exception ex) { - var creationDate = File.GetCreationTime(file); - if ((DateTime.Now - creationDate).TotalDays > job.RetentionDays) - File.Delete(file); + logger.LogError(ex, "Backup '{name}' failed.", job.Name); } - - Directory.Delete(currentDir, recursive: true); } - private static bool IsLocalDrivePath(string path) + +private static bool IsLocalDrivePath(string path) { if (!Path.IsPathRooted(path)) { @@ -188,7 +237,7 @@ private void LogResults(string jobName, List results) } } - public static void CopyDirectory( + public static async Task CopyDirectory( string sourceDir, string targetDir, CancellationToken cancellationToken) @@ -207,7 +256,9 @@ public static void CopyDirectory( cancellationToken.ThrowIfCancellationRequested(); var directoryName = Path.GetFileName(directory); var targetSubdir = Path.Combine(targetDir, directoryName); - CopyDirectory(directory, targetSubdir, cancellationToken); + //CopyDirectory(directory, targetSubdir, cancellationToken); + await CopyDirectory(directory, targetSubdir, cancellationToken); + } } diff --git a/appsettings.json b/appsettings.json index 93b4ddb..9556122 100644 --- a/appsettings.json +++ b/appsettings.json @@ -10,10 +10,10 @@ "MinimumLevel": "Information" }, "BackupOptions": { - "Host": "FTP_HOST", - "Username": "FTP_USERNAME", - "Password": "FTP_PASSWORD", - "LocalPath": "/", + "Host": "taskbeat.pl", + "Username": "dmin@taskbeat.pl", + "Password": "Lq3HsTLJHUvg3PfpvQv6", + "LocalPath": "D:\\Backups\\Sample", "RunAt": "02:00", "HistoryCopies": 5, "DefaultTimeoutMinutes": 60, @@ -23,12 +23,12 @@ "Backups": [ { "Name": "EXAMPLE_NAME", - "Host": "FTP_HOST", + "Host": "taskbeat.pl", "Port": 21, - "Username": "FTP_USERNAME", - "Password": "FTP_PASSWORD", - "RemotePath": "/", - "LocalPath": "/", + "Username": "admin@taskbeat.pl", + "Password": "Lq3HsTLJHUvg3PfpvQv6", + "RemotePath": "/public_html/images", + "LocalPath": "D:\\Backups\\Sample", "Encryption": "Explicit", "Passive": true, "AllowInvalidCertificate": true, From 783149d01b58115f62fb91b6ca6b1666184d0fc4 Mon Sep 17 00:00:00 2001 From: Luck16048 <140838564+Luck16048@users.noreply.github.com> Date: Sat, 31 Jan 2026 15:19:07 +0100 Subject: [PATCH 7/8] Update appsettings.json --- appsettings.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/appsettings.json b/appsettings.json index 9556122..93b4ddb 100644 --- a/appsettings.json +++ b/appsettings.json @@ -10,10 +10,10 @@ "MinimumLevel": "Information" }, "BackupOptions": { - "Host": "taskbeat.pl", - "Username": "dmin@taskbeat.pl", - "Password": "Lq3HsTLJHUvg3PfpvQv6", - "LocalPath": "D:\\Backups\\Sample", + "Host": "FTP_HOST", + "Username": "FTP_USERNAME", + "Password": "FTP_PASSWORD", + "LocalPath": "/", "RunAt": "02:00", "HistoryCopies": 5, "DefaultTimeoutMinutes": 60, @@ -23,12 +23,12 @@ "Backups": [ { "Name": "EXAMPLE_NAME", - "Host": "taskbeat.pl", + "Host": "FTP_HOST", "Port": 21, - "Username": "admin@taskbeat.pl", - "Password": "Lq3HsTLJHUvg3PfpvQv6", - "RemotePath": "/public_html/images", - "LocalPath": "D:\\Backups\\Sample", + "Username": "FTP_USERNAME", + "Password": "FTP_PASSWORD", + "RemotePath": "/", + "LocalPath": "/", "Encryption": "Explicit", "Passive": true, "AllowInvalidCertificate": true, From 189ca56c45275e490a652cc676637afa0f727ace Mon Sep 17 00:00:00 2001 From: Luck16048 Date: Tue, 3 Feb 2026 18:49:31 +0100 Subject: [PATCH 8/8] Update backup rotate, changing the behavior of the timeout configuration. --- FtpBackupRunner.cs | 64 +++++++++------------------------------------- Program.cs | 2 +- appsettings.json | 23 ++++++++--------- 3 files changed, 24 insertions(+), 65 deletions(-) diff --git a/FtpBackupRunner.cs b/FtpBackupRunner.cs index f6072bf..303598d 100644 --- a/FtpBackupRunner.cs +++ b/FtpBackupRunner.cs @@ -47,15 +47,12 @@ public async Task RunJobAsync( return; } - var currentRoot = Path.Combine( - job.LocalPath, - options.CurrentSubdirectoryName); - var historyRoot = Path.Combine( - job.LocalPath, - options.HistorySubdirectoryName); - - Directory.CreateDirectory(currentRoot); - Directory.CreateDirectory(historyRoot); + var tempDir = Path.Combine(job.LocalPath, "temp", job.Host); + Directory.CreateDirectory(tempDir); + + var archiveDir = Path.Combine(job.LocalPath, job.Host); + Directory.CreateDirectory(archiveDir); + using var client = new FtpClient(job.Host, job.Username, job.Password, job.Port); @@ -101,7 +98,7 @@ public async Task RunJobAsync( remotePath); var results = client.DownloadDirectory( - currentRoot, + tempDir, remotePath, FtpFolderSyncMode.Mirror, FtpLocalExists.Overwrite, @@ -109,27 +106,6 @@ public async Task RunJobAsync( LogResults(job.Name, results); - var snapshotName = DateTimeOffset.Now - .ToString("yyyyMMdd_HHmmss", CultureInfo.InvariantCulture); - var snapshotPath = Path.Combine(historyRoot, snapshotName); - - logger.LogInformation( - "Creating history snapshot for backup '{name}' at {path}.", - job.Name, - snapshotPath); - - - CopyDirectory(currentRoot, snapshotPath, completionToken); - CleanupHistory(historyRoot, job, options); - - string currentDir = Path.Combine(job.LocalPath, "current"); - Directory.CreateDirectory(currentDir); - - CopyDirectory(snapshotPath, currentDir, completionToken); - - string archiveDir = Path.Combine(job.LocalPath, "archives", job.Name); - Directory.CreateDirectory(archiveDir); - string zipPath = Path.Combine(archiveDir, DateTime.Now.ToString("yyyy-MM-dd") + ".zip"); if (File.Exists(zipPath)) @@ -137,7 +113,7 @@ public async Task RunJobAsync( File.Delete(zipPath); } - ZipFile.CreateFromDirectory(currentDir, zipPath, CompressionLevel.Optimal, includeBaseDirectory: false); + ZipFile.CreateFromDirectory(tempDir, zipPath, CompressionLevel.Optimal, includeBaseDirectory: false); foreach (var file in Directory.GetFiles(archiveDir, "*.zip")) { @@ -145,27 +121,11 @@ public async Task RunJobAsync( if ((DateTime.Now - creationDate).TotalDays > job.RetentionDays) File.Delete(file); } - - foreach (var file in Directory.GetFiles(currentDir)) - File.Delete(file); - - foreach (var dir in Directory.GetDirectories(currentDir)) - Directory.Delete(dir, recursive: true); - - using var operationCts = new CancellationTokenSource(TimeSpan.FromMinutes(job.OperationTimeoutMinutes)); - using var opLinkedCts = CancellationTokenSource.CreateLinkedTokenSource(completionToken, operationCts.Token); - - string host = job.Host; - string username = job.Username; - string password = job.Password; - var ftpClient = new FtpClient(); - ftpClient.Host = host; - ftpClient.Credentials = new NetworkCredential(username, password); - ftpClient.Connect(); - string localPath = Path.Combine(job.LocalPath, "current"); - ftpClient.DownloadDirectory(remotePath, localPath); - ftpClient.Disconnect(); + if (Directory.Exists(tempDir)) + { + Directory.Delete(tempDir, recursive: true); + } } catch (OperationCanceledException) { diff --git a/Program.cs b/Program.cs index ad772c0..5659607 100644 --- a/Program.cs +++ b/Program.cs @@ -54,7 +54,7 @@ Console.WriteLine("I'm starting a test of downloading files from FTP"); await ftpRunner.RunJobAsync(backupJob, backupOptions, CancellationToken.None); - Console.WriteLine("FTP test completed! Files should be in /"); + Console.WriteLine("FTP test completed!" ); } catch (Exception ex) { diff --git a/appsettings.json b/appsettings.json index 9556122..da7d60b 100644 --- a/appsettings.json +++ b/appsettings.json @@ -10,29 +10,28 @@ "MinimumLevel": "Information" }, "BackupOptions": { - "Host": "taskbeat.pl", - "Username": "dmin@taskbeat.pl", - "Password": "Lq3HsTLJHUvg3PfpvQv6", - "LocalPath": "D:\\Backups\\Sample", + "Host": "FTP_HOST", + "Username": "FTP_USERNAME", + "Password": "FTP_PASSWORD", + "LocalPath": "/", "RunAt": "02:00", "HistoryCopies": 5, "DefaultTimeoutMinutes": 60, "RetentionDays": 7, - "CurrentSubdirectoryName": "current", - "HistorySubdirectoryName": "_history", "Backups": [ { "Name": "EXAMPLE_NAME", - "Host": "taskbeat.pl", + "Host": "FTP_HOST", "Port": 21, - "Username": "admin@taskbeat.pl", - "Password": "Lq3HsTLJHUvg3PfpvQv6", - "RemotePath": "/public_html/images", - "LocalPath": "D:\\Backups\\Sample", + "Username": "FTP_USERNAME", + "Password": "FTP_PASSWORD", + "RemotePath": "/", + "LocalPath": "/", "Encryption": "Explicit", "Passive": true, "AllowInvalidCertificate": true, - "TimeoutMinutes": 60, + "OperationTimeoutMinutes": 10, + "CompletionTimeoutMinutes": 180, "HistoryCopies": 5 } ]