.NET library
The engine behind the command line is published on NuGet as Corsinvest.ProxmoxVE.NodeProtect.Api, for .NET 8, 9 and 10. cv4pve-admin uses the same package.
dotnet add package Corsinvest.ProxmoxVE.NodeProtect.ApiWhat it does, and what it leaves to you
Section titled “What it does, and what it leaves to you”ProtectEngine backs up one node at a time: it connects with the SSH.NET ConnectionInfo you pass,
runs the same tar command as the tool (What happens on each node)
and writes the archive to a file or a stream.
ProtectHelper gives the rest of the tool’s layout (host parsing, archive names, which dated folders
retention deletes), so your application can store backups the way the tool does. The loop over the
nodes, the credentials and what to do when one node fails stay yours.
Example
Section titled “Example”using Corsinvest.ProxmoxVE.NodeProtect.Api;using Microsoft.Extensions.Logging;using Renci.SshNet;
using var loggerFactory = LoggerFactory.Create(b => b.AddConsole());var engine = new ProtectEngine(loggerFactory.CreateLogger<ProtectEngine>());
var key = new PrivateKeyFile("/root/.ssh/node-protect");var connection = new ConnectionInfo("pve01", 22, "root", new PrivateKeyAuthenticationMethod("root", key)){ Timeout = TimeSpan.FromSeconds(30)};
var workDir = "/srv/node-protect";var folder = Path.Combine(workDir, DateTime.Now.ToString(ProtectEngine.DateFormat));Directory.CreateDirectory(folder);
string[] paths = ["/etc/.", "/etc/pve/.", "/var/lib/pve-cluster/."];var summary = await engine.BackupNodeAsync("pve01", connection, paths, Path.Combine(folder, ProtectHelper.GetArchiveFileName("pve01")));Console.WriteLine(summary); // [pve01] tar.gz streamed in 1.23 sec
// Retention as --keep=7 does it: only dated folders are counted and deletedforeach (var old in ProtectHelper.GetBackupsToDelete(Directory.GetDirectories(workDir), 7)){ Directory.Delete(old, true);}
// Or into any stream: memory, upload, encryption…await using var stream = new MemoryStream();await engine.BackupNodeToStreamAsync("pve01", connection, paths, stream);ProtectEngine
Section titled “ProtectEngine”ProtectEngine(ILogger<ProtectEngine> logger) logs at Debug level the command run on the node and
the time taken, and at Warning level every line tar writes to its error output: paths it left out,
files that changed while read.
| Member | Description |
|---|---|
BackupNodeAsync(node, connectionInfo, paths, targetFilePath, cancellationToken) |
Creates targetFilePath (its folder must exist) and writes the archive into it; on Linux and macOS the file gets mode 600. On any error the partial file is deleted and the exception is thrown again. |
BackupNodeToStreamAsync(node, connectionInfo, paths, destination, cancellationToken) |
Writes the archive into destination, which is not disposed. Nothing is written to disk. |
FileNameSuffix |
"-config.tar.gz", the suffix the tool uses for archive names. |
DateFormat |
"yyyy-MM-dd-HH-mm-ss", the format of the tool’s dated folders. |
node is used only in logs and messages. Both methods return a short summary like
[pve01] tar.gz streamed in 1.23 sec.
Exceptions:
InvalidOperationExceptionwhentaron the node exits with code 2 or more; the message contains its error output. Exit code 1 (a file changed while read) is accepted.ArgumentExceptionwhen a path contains a single quote, before connecting to the node.- The SSH.NET exceptions for connection and authentication errors.
The same cautions as the tool apply: paths that do not exist or cannot be read are left out with a
warning on the logger, not an exception, and the host key is not checked: the SshClient is created
inside the engine, so the caller cannot add a check. See SSH access and security.
ProtectHelper
Section titled “ProtectHelper”| Member | Description |
|---|---|
ParseHosts(hosts) |
Parses a comma-separated --host value into host and port pairs, in order; empty entries are skipped. |
ParseHostAndPort(hostAndPort) |
Parses one host[:port]: host name, IPv4, IPv6 in brackets with or without port, bare IPv6 without port. Throws ArgumentException for a malformed bracketed entry. |
DefaultPort |
22, the port used when an entry has none. |
GetArchiveFileName(host) |
<host>-config.tar.gz, with the characters not allowed in file names on the current system replaced by _ (the : of IPv6 on Windows). |
IsBackupDirectoryName(name) |
Whether a folder name has the DateFormat shape, such as 2026-09-29-03-00-01. |
GetBackupsToDelete(directories, keep) |
The dated folders beyond the newest keep, as the tool deletes them. Folders with other names are never returned. |