diff --git a/ConfigData.cs b/ConfigData.cs
deleted file mode 100644
index c7df2f1..0000000
--- a/ConfigData.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-namespace OneDriveBackupService;
-
-public sealed class ConfigData {
- public string Schedule { get; }
- public string BackupUploadRoot { get; }
- public string LocalRoot { get; }
- public string IncludeFile { get; }
- public int KeepLast { get; }
-
- public ConfigData(IConfiguration config) {
- Schedule = config["Schedule"]!;
- BackupUploadRoot = config["UploadRoot"]!;
- LocalRoot = config["LocalRoot"]!;
- IncludeFile = config["IncludeFile"]!;
- KeepLast = int.Parse(config["KeepLast"]!);
- }
-}
\ No newline at end of file
diff --git a/Models/ConfigData.cs b/Models/ConfigData.cs
new file mode 100644
index 0000000..82b9987
--- /dev/null
+++ b/Models/ConfigData.cs
@@ -0,0 +1,10 @@
+namespace OneDriveBackupService.Models;
+
+public sealed class ConfigData(IConfiguration config) {
+ public string Schedule { get; } = config["Schedule"]!;
+ public string BackupUploadRoot { get; } = config["UploadRoot"]!;
+ public string LocalRoot { get; } = config["LocalRoot"]!;
+ public string IncludeFile { get; } = config["IncludeFile"]!;
+ public int KeepLast { get; } = int.Parse(config["KeepLast"]!);
+ public string OneDriveApiUrl { get; } = config["OneDriveApiUrl"]!;
+}
\ No newline at end of file
diff --git a/Models/OneDriveItem.cs b/Models/OneDriveItem.cs
new file mode 100644
index 0000000..0ffc9a1
--- /dev/null
+++ b/Models/OneDriveItem.cs
@@ -0,0 +1,7 @@
+namespace OneDriveBackupService.Models;
+
+public sealed class OneDriveItem {
+ public string Id { get; set; } = null!;
+ public string Name { get; set; } = null!;
+ public DateTime Created { get; set; }
+}
diff --git a/OneDrive/OneDriveClient.cs b/OneDrive/OneDriveClient.cs
new file mode 100644
index 0000000..b11edeb
--- /dev/null
+++ b/OneDrive/OneDriveClient.cs
@@ -0,0 +1,27 @@
+using OneDriveBackupService.Models;
+
+namespace OneDriveBackupService.OneDrive;
+
+public sealed class OneDriveClient(ConfigData config) {
+ public void UploadFile(string filePath) {
+ Directory.CreateDirectory(config.BackupUploadRoot);
+ var destFileName = Path.Combine(config.BackupUploadRoot, Path.GetFileName(filePath));
+ File.Move(filePath, destFileName);
+ }
+
+ public int DeleteOldFiles() {
+ var directory = new DirectoryInfo(config.BackupUploadRoot);
+
+ var filesToDelete = directory.EnumerateFiles()
+ .Where(f => f.Name.StartsWith("backup_") && f.Name.EndsWith(".tar.gz"))
+ .OrderByDescending(f => f.CreationTimeUtc)
+ .Skip(config.KeepLast)
+ .ToArray();
+
+ foreach (var file in filesToDelete) {
+ file.Delete();
+ }
+
+ return filesToDelete.Length;
+ }
+}
\ No newline at end of file
diff --git a/OneDriveBackupService.csproj b/OneDriveBackupService.csproj
index b696821..9f91c7a 100644
--- a/OneDriveBackupService.csproj
+++ b/OneDriveBackupService.csproj
@@ -9,9 +9,7 @@
-
-
diff --git a/OneDriveClient.cs b/OneDriveClient.cs
deleted file mode 100644
index 79e8186..0000000
--- a/OneDriveClient.cs
+++ /dev/null
@@ -1,87 +0,0 @@
-using Azure.Identity;
-using Microsoft.Graph;
-using Microsoft.Graph.Drives.Item.Items.Item.CreateUploadSession;
-using Microsoft.Graph.Models;
-
-namespace OneDriveBackupService;
-
-public class OneDriveClient {
-
- private readonly ConfigData _config;
- private readonly GraphServiceClient _client;
-
- public OneDriveClient(ConfigData config) {
- _config = config;
-
- var options = new DeviceCodeCredentialOptions {
- TenantId = "consumers",
- DeviceCodeCallback = (code, _) => {
- Console.WriteLine(code.Message);
- return Task.CompletedTask;
- }
- };
-
- _client = new GraphServiceClient(new DeviceCodeCredential(options), ["Files.ReadWrite.All"]);
- }
-
- public async Task EnsureAuthenticated(CancellationToken token) {
- await _client.Me.Drive.GetAsync(cancellationToken: token);
- }
-
- public async Task> UploadFile(string filePath, CancellationToken token) {
- var fileName = Path.GetFileName(filePath);
- var remoteFilePath = _config.BackupUploadRoot.Trim('/') + '/' + fileName;
-
- var defaultDrive = await _client.Me.Drive.GetAsync(cancellationToken: token);
-
- var driveFile = _client.Drives[defaultDrive!.Id].Items[$"root:/{remoteFilePath}:"]!;
- var uploadSession = await driveFile.CreateUploadSession.PostAsync(new CreateUploadSessionPostRequestBody {
- Item = new DriveItemUploadableProperties {
- AdditionalData = new Dictionary {
- { "@microsoft.graph.conflictBehavior", "replace" }
- }
- }
- }, cancellationToken: token);
-
- await using var stream = File.OpenRead(filePath);
- var uploader = new LargeFileUploadTask(uploadSession, stream);
- var result = await uploader.UploadAsync(cancellationToken: token);
-
- return result;
- }
-
- public async Task DeleteOldFiles(CancellationToken token) {
- var defaultDrive = await _client.Me.Drive.GetAsync(cancellationToken: token);
-
- var remoteFolder = _config.BackupUploadRoot.Trim('/');
- var backupFiles = await _client.Drives[defaultDrive!.Id]
- .Items[$"root:/{remoteFolder}:"]
- .Children.GetAsync(cancellationToken: token);
-
- if (backupFiles == null || backupFiles.Value == null || backupFiles.Value.Count == 0)
- return 0;
-
- var sortedFiles = backupFiles.Value
- .Where(i =>
- i.File != null &&
- i.Name != null &&
- i.Name.StartsWith("backup_") &&
- i.Name.EndsWith(".tar.gz"))
- .OrderByDescending(i => i.CreatedDateTime)
- .ToList();
-
- if (sortedFiles.Count < _config.KeepLast)
- return 0;
-
- int deletedCount = 0;
- foreach (var backupFile in sortedFiles.Skip(_config.KeepLast)) {
- if (backupFile.Id == null) continue;
-
- await _client.Drives[defaultDrive.Id].Items[backupFile.Id].DeleteAsync(cancellationToken: token);
- deletedCount++;
- }
-
- return deletedCount;
- }
-
-}
\ No newline at end of file
diff --git a/Program.cs b/Program.cs
index b565dd5..ac04c1c 100644
--- a/Program.cs
+++ b/Program.cs
@@ -1,4 +1,6 @@
using OneDriveBackupService;
+using OneDriveBackupService.Models;
+using OneDriveBackupService.OneDrive;
var builder = Host.CreateApplicationBuilder(args);
diff --git a/README.md b/README.md
index 55e2901..6f2aba9 100644
--- a/README.md
+++ b/README.md
@@ -17,10 +17,7 @@ Unterstützt Cron-basierte Backups sowie manuelles Triggern über `docker exec`.
- Automatische Backups nach Cron-Schedule (`appsettings.json` / ENV)
- Upload zu OneDrive über Microsoft Graph SDK
-- Behalten nur der letzten N Backups (`KeepLast`)
- Unterstützung für manuelles Backup via `docker exec`
-- Lokale Zeitzone für Logs und Backup-Zeitstempel
-- Flexible Konfiguration über ENV oder `appsettings.json`
---
@@ -29,13 +26,9 @@ Unterstützt Cron-basierte Backups sowie manuelles Triggern über `docker exec`.
Um ein Backup manuell auf einem laufenden Container auszuführen:
```bash
-docker exec -it onedrive-backup-server dotnet OneDriveBackupService.dll --run-once
+docker exec -it backup-worker dotnet OneDriveBackupService.dll --run-once
```
-* Das löst **ein sofortiges Backup** aus
-* Cron-Loop des Hauptcontainers bleibt ungestört
-* Backup-Dateien werden wie üblich nach OneDrive hochgeladen
-
---
## Umgebungsvariablen
@@ -47,16 +40,5 @@ docker exec -it onedrive-backup-server dotnet OneDriveBackupService.dll --run-on
| `LocalRoot` | Lokaler Datenpfad für Backups |
| `IncludeFile` | Textdatei mit allen unterordnern, die mit ins Backup sollen |
| `KeepLast` | Anzahl zu behaltender Backups |
-| `TenantId` | Azure TenantId |
-| `ClientId` | Azure ClientId |
-| `ClientSecret` | Azure Client Secret |
-| `UserId` | OneDrive User Id |
| `TZ` | Zeitzone für Logs / DateTime.Now (optional) |
----
-
-## Hinweise
-
-* Die Backup-Dateien erhalten eindeutige Namen mit Timestamp: `backup_YYYYMMDD_HHMMSS.tar.gz`
-* Alte Backups werden automatisch gelöscht, basierend auf `KeepLast`
-* `docker exec --run-once` löst ein manuelles Backup aus, ohne den Cron-Loop zu stoppen
diff --git a/Worker.cs b/Worker.cs
index 2e311d6..b890297 100644
--- a/Worker.cs
+++ b/Worker.cs
@@ -1,15 +1,16 @@
using System.Diagnostics;
using Cronos;
+using OneDriveBackupService.Models;
+using OneDriveBackupService.OneDrive;
namespace OneDriveBackupService;
public class Worker(ILogger logger, ConfigData config, OneDriveClient client) : BackgroundService {
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
- await client.EnsureAuthenticated(stoppingToken);
-
if (Environment.GetCommandLineArgs().Contains("--run-once")) {
logger.LogInformation("Manual backup triggered");
await RunBackup(DateTime.Now, stoppingToken);
+ Environment.Exit(0);
return;
}
@@ -25,12 +26,12 @@ public class Worker(ILogger logger, ConfigData config, OneDriveClient cl
delay = TimeSpan.Zero;
if (!cronTime.HasValue) {
- logger.LogError("Cron expression falied, falling back to default delay");
+ logger.LogError("Cron expression failed, falling back to default delay");
delay = TimeSpan.FromHours(12);
}
var nextRun = DateTime.Now + delay;
- logger.LogInformation("Next backup run: {time}", nextRun.ToString("f"));
+ logger.LogInformation("Next backup: {time}", nextRun.ToString("f"));
await Task.Delay(delay, stoppingToken);
await RunBackup(nextRun, stoppingToken);
@@ -45,20 +46,10 @@ public class Worker(ILogger logger, ConfigData config, OneDriveClient cl
logger.LogError("Backup archive creation failed");
return;
}
-
- logger.LogInformation("Backup archive created, starting upload...");
- var uploadResult = await client.UploadFile(file, stoppingToken);
- if (!uploadResult.UploadSucceeded) {
- logger.LogError("Upload failed");
- File.Delete(file);
- return;
- }
-
- logger.LogInformation("Upload completed");
+ client.UploadFile(file);
- File.Delete(file);
- var count = await client.DeleteOldFiles(stoppingToken);
+ var count = client.DeleteOldFiles();
logger.LogInformation("Deleted {count} old backups", count);
logger.LogInformation("Backup completed");
@@ -77,7 +68,7 @@ public class Worker(ILogger logger, ConfigData config, OneDriveClient cl
"-C",
config.LocalRoot,
"-T",
- Path.GetRelativePath(config.LocalRoot, config.IncludeFile)
+ Path.Combine(config.LocalRoot, config.IncludeFile)
},
RedirectStandardOutput = true,
RedirectStandardError = true
@@ -88,6 +79,9 @@ public class Worker(ILogger logger, ConfigData config, OneDriveClient cl
await process.WaitForExitAsync(stoppingToken);
if (process.ExitCode != 0) {
+ var error = await process.StandardError.ReadToEndAsync(stoppingToken);
+ logger.LogError(error);
+
return string.Empty;
}
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..011885b
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,29 @@
+services:
+ backup:
+ image: registry.leon-hoppe.de/leon.hoppe/onedrivebackupservice:latest
+ container_name: backup-worker
+ user: root
+ environment:
+ TZ: Europe/Berlin
+ Schedule: 0 3 * * 1
+ UploadRoot: /data/Server/Backups
+ LocalRoot: /backups
+ IncludeFile: include.txt
+ KeepLast: 2
+ volumes:
+ - /home/leon:/backups:ro
+ - ./data:/data:rw
+ - ./cache:/tmp:rw
+
+ sync:
+ image: driveone/onedrive:edge
+ container_name: backup-sync
+ volumes:
+ - ./config:/onedrive/conf:rw
+ - ./data:/onedrive/data:rw
+ environment:
+ - ONEDRIVE_UID=1000
+ - ONEDRIVE_GID=1000
+ - ONEDRIVE_UPLOADONLY=1
+ - ONEDRIVE_NOREMOTEDELETE=1
+ - ONEDRIVE_AUTHFILES=/onedrive/conf/auth-url:/onedrive/conf/auth-response
\ No newline at end of file