| .cursor | ||
| docs | ||
| src | ||
| utils | ||
| .editorconfig | ||
| .gitattributes | ||
| .gitignore | ||
| AGENTS.md | ||
| CHANGELOG.md | ||
| CONTRIBUTING.md | ||
| Directory.Build.props | ||
| LICENSE.md | ||
| README.md | ||
MaksIT.LTO.Backup
Cross-platform toolkit for LTO tape backup and restore. Console, Avalonia UI, and Worker share one service layer.
See LICENSE.md (GPLv2). Changes: CHANGELOG.md. Contributing: CONTRIBUTING.md.
| Host | Project | Role |
|---|---|---|
| Console | MaksIT.LTO.Backup |
Interactive menu / backup --name CLI |
| Avalonia | MaksIT.LTO.Backup.UI |
MVVM operations UI (Windows + Linux; CommunityToolkit.Mvvm) |
| Worker | MaksIT.LTO.Backup.Service |
Scheduled library backups (Windows service / systemd) |
Device access modes:
- Physical — real drive/library on Windows (
\\.\Tape0,\\.\Changer0; admin) or Linux (/dev/nst0,/dev/sg*;tape/diskgroup) - Emulated — file-backed drive + library (no hardware; primary CI / no-hardware path)
Current line is
0.1.0-alpha.2(prerelease, not production-ready). Emulator coverage is the primary validation path; physical tape/changer and Linux SMB are not hardware-proven yet. Use at your own risk.
If you find this project useful, please consider supporting its development:
Features
- Backup and restore with AES-GCM encrypted on-tape JSON descriptors and SHA-256 file verification
- SchemaVersion 2 layout: BOT index (
MLTI), payload, filemark, descriptor preamble (MLTD), ciphertext, double filemark - Overwrite or Append write modes (multiple sets per cartridge via BOT set table)
- Fail-fast restore on checksum mismatch (
FailFastOnChecksumMismatch) - MAM summary attributes written after backup (app name/version, host, text label)
- Local paths on all platforms; SMB source/destination on Windows (WNet) and Linux (SMB2 staging via SMBLibrary)
- LTO generation block sizes (LTO1–LTO9) and media-capacity checks
- Drive ops: load, eject, short erase, status, cartridge memory (MAM) / barcode
- Library ops: inventory, move medium (emulated; physical Windows
IOCTL_CHANGER_*and Linux SG_IO) - Shared library used by console, Avalonia UI, and Worker
- Live Avalonia monitor for drive status and library inventory
- Avalonia Service tab: install / uninstall / start / stop Worker (Windows + Linux)
- Emulator-backed unit / E2E tests without a physical tape (
{tapeId}.drive.json+{tapeId}.blocks.bin)
Requirements
- .NET 10 SDK
- Windows: physical tape/changer IOCTLs; Avalonia UI; SMB via WNet
- Linux: physical tape (
/dev/nst*) and changer (/dev/sg*); Avalonia UI; SMB via managed SMB2 client; orDeviceMode=Emulated - Optional: PowerShell 7+ for RepoUtils test/release engines under
utils/
Solution layout
utils/ # RepoUtils release / test engines
src/
MaksIT.LTO.slnx
MaksIT.LTO.Core/ # tape/library APIs + emulators
MaksIT.LTO.Backup.Shared/ # shared config, orchestrators, host helpers
MaksIT.LTO.Backup/ # console host
MaksIT.LTO.Backup.UI/ # Avalonia host (Windows + Linux)
MaksIT.LTO.Backup.Service/ # scheduled Worker (Windows / Linux)
MaksIT.LTO.Tests/ # emulator tests
Scheduled backups (library only)
Automatic scheduling runs only when Topology is TapeLibrary. StandaloneDrive remains manual (console / UI).
Backup jobs define what (source, Tapes pool, LTO gen). Drive selection is automatic.
Aggregations define when and in what order jobs run as one wave (e.g. Night / Afternoon):
JobNames— ordered list of backup job namesSchedule— UScheduler-compatible calendar with overdue catch-up (RunMonth,RunWeekday,RunTimeUTCHH:mm,MinIntervalMinutes)Disabled/LastRunUtc— wave-level
The Worker holds an exclusive lease for the whole aggregation (no mid-wave collisions). If another wave is busy, the due wave retries on the next poll without consuming LastRunUtc. After each job: unload cartridge so the next job can claim any empty drive.
Worker lifecycle (also from Avalonia Service tab):
MaksIT.LTO.Backup.Service --install
MaksIT.LTO.Backup.Service --start
MaksIT.LTO.Backup.Service --stop
MaksIT.LTO.Backup.Service --uninstall
Console:
MaksIT.LTO.Backup backup --name "Normal test"
MaksIT.LTO.Backup backup --aggregation "Night"
MaksIT.LTO.Backup backup --aggregation "Night" --scheduled
Install/uninstall typically require administrator (Windows) or root (Linux systemd).
Configuration
One shared model for the whole solution. Seed JSON ships next to each host; runtime writes go to AppData so Program Files does not need elevation.
- Model:
src/MaksIT.LTO.Backup.Shared/Models/Configuration.cs - Seed:
src/MaksIT.LTO.Backup.Shared/configuration.json(copied next to the exe, never written) - Runtime:
%AppData%/MaksIT/LTO Backup/settings.json(same folder name as WiX:Program Files\MaksIT\LTO Backup) - Loader / workflows:
ConfigurationFileService,BackupOrchestrator,LibraryOrchestratorinMaksIT.LTO.Backup.Shared
Edit the Shared seed for factory defaults, or use the UI Settings tab and click Save configuration.
DeviceMode selects the backend (Physical / Emulated).
Topology selects how hardware is used:
StandaloneDrive— Drive tab only (load/eject/status/MAM). Backup tab runs against that drive.TapeLibrary— Library tab (inventory, move into drive bays, MAM on loaded cartridge). Backup tab runs only when a cartridge is in a drive bay.
{
"Configuration": {
"DeviceMode": "Emulated",
"Topology": "StandaloneDrive",
"TapePath": "\\\\.\\Tape0",
"LibraryPath": "\\\\.\\Changer0",
"WriteDelay": 100,
"FailFastOnChecksumMismatch": true,
"Emulator": {
"DataDirectory": ".\\emulator-data",
"Library": { "SlotCount": 24, "DriveCount": 2, "IePortCount": 1 }
},
"Backups": [
{
"Name": "Normal test",
"Barcode": "LTO001",
"LTOGen": "LTO5",
"WriteMode": "Overwrite",
"Source": { "LocalPath": { "Path": "F:\\LTO\\Backup" } },
"Destination": { "LocalPath": { "Path": "F:\\LTO\\Restore" } }
}
]
}
}
On-tape format (SchemaVersion 2)
[BOT index MLTI] → [file blocks…] → FM → [MLTD preamble] → [length-prefixed AES-GCM descriptor] → FM FM
Append adds another payload+descriptor set at EOD and rewrites the BOT set table (max 16 sets).
Descriptor secret
On first run a secret.txt is created for descriptor encryption (first line = current key). Keep a safe copy.
To rotate: put the new key on line 1 of secret.txt and move the old key to line 2+ or into secret.history.txt. Decrypt tries the current key then prior keys.
To avoid leaving the file on disk, set a machine environment variable (used as the preferred encrypt/decrypt key):
[System.Environment]::SetEnvironmentVariable(
"LTO_BACKUP_SECRET",
"<secret.txt content here>",
[System.EnvironmentVariableTarget]::Machine
)
Console menu
- Load tape
- Backup
- Restore
- Eject tape
- Get device status
- Tape erase (short)
- Read cartridge memory
- Move medium (library)
- Library inventory
- Exit
Build and run
cd src
dotnet build MaksIT.LTO.slnx
dotnet run --project .\MaksIT.LTO.Backup
Avalonia UI (Windows or Linux):
dotnet run --project .\MaksIT.LTO.Backup.UI
Prefer RepoUtils for a GitHub release (utils\Invoke-ReleasePackage.bat). Assets are siblings: portable maksit-lto-backup-{version}.zip (win-x64 console, Avalonia UI, Worker), Windows setup maksit-lto-backup-{version}.exe (Avalonia UI), and maksit-lto-backup-{version}.flatpak (Avalonia UI). The installer and Flatpak are not inside the zip. Manual publish:
cd src
dotnet publish .\MaksIT.LTO.Backup -c Release -o ..\releases\console
dotnet publish .\MaksIT.LTO.Backup.UI -c Release -o ..\releases\ui
dotnet publish .\MaksIT.LTO.Backup.Service -c Release -o ..\releases\service
Physical mode: Windows needs elevation; Linux needs access to /dev/nst* / /dev/sg* (often tape/disk group). Discover changer nodes with lsscsi -g.
Tests and coverage
Emulator-backed tests (no tape required):
cd src
dotnet test .\MaksIT.LTO.Tests
With Cobertura coverage:
cd src
dotnet test .\MaksIT.LTO.Tests --collect:"XPlat Code Coverage" --results-directory .\TestResults
Coverage shields at the top of this README use shields.io and are rewritten by the CoverageBadges plugin when utils\Invoke-TestEngine.bat runs.
RepoUtils release pipeline
Vendored under utils/ (from MaksIT RepoUtils community):
| Action | Entry |
|---|---|
| Test | utils\Invoke-TestEngine.bat |
| Release | utils\Invoke-ReleasePackage.bat |
GitHub assets are siblings: portable maksit-lto-backup-{version}.zip (win-x64), Windows setup exe (Avalonia UI), and Flatpak (Avalonia UI). The installer and Flatpak are not inside the zip. On Windows the Flatpak bundle is built via WSL Debian.
Settings:
utils/engines/test/scriptSettings.jsonutils/engines/release/scriptSettings.json
License
GPLv2 — see LICENSE.md.
© Maksym Sadovnychyy (MAKS-IT)
