├── icon.png ├── .editorconfig ├── manifests └── c │ └── Corsinvest │ └── cv4pve │ └── autosnap │ └── 1.17.0 │ ├── Corsinvest.cv4pve.autosnap.yaml │ ├── Corsinvest.cv4pve.autosnap.installer.yaml │ └── Corsinvest.cv4pve.autosnap.locale.en-US.yaml ├── Directory.Build.props ├── Directory.Packages.props ├── src ├── Corsinvest.ProxmoxVE.AutoSnap.Api │ ├── ResultSnapVm.cs │ ├── ResultBaseSnap.cs │ ├── ResultSnap.cs │ ├── HookPhase.cs │ ├── Corsinvest.ProxmoxVE.AutoSnap.Api.csproj │ ├── PhaseEventArgs.cs │ └── Application.cs └── Corsinvest.ProxmoxVE.AutoSnap │ ├── Program.cs │ ├── Corsinvest.ProxmoxVE.AutoSnap.csproj │ └── Commands.cs ├── 3rd-party-licenses.md ├── .github ├── workflows │ ├── publish.yml │ ├── build.yml │ └── quality.yml └── ISSUE_TEMPLATE │ └── bug_report.yml ├── .vscode ├── launch.json └── tasks.json ├── hooks ├── hook-template.bat ├── hook-template.ps1 ├── hook-template.sh └── send-metrics.sh ├── Corsinvest.ProxmoxVE.AutoSnap.sln ├── .gitignore ├── README.md └── LICENSE.md /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Corsinvest/cv4pve-autosnap/HEAD/icon.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # All files 2 | [*] 3 | guidelines = 80 4 | 5 | # C# or VB files 6 | [*.{cs,vb}] 7 | guidelines = 80, 120 -------------------------------------------------------------------------------- /manifests/c/Corsinvest/cv4pve/autosnap/1.17.0/Corsinvest.cv4pve.autosnap.yaml: -------------------------------------------------------------------------------- 1 | # Created using wingetcreate 1.10.3.0 2 | # yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.10.0.schema.json 3 | 4 | PackageIdentifier: Corsinvest.cv4pve.autosnap 5 | PackageVersion: 1.17.0 6 | DefaultLocale: en-US 7 | ManifestType: version 8 | ManifestVersion: 1.10.0 9 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.17.0 4 | latest 5 | enable 6 | enable 7 | Corsinvest Srl 8 | Corsinvest Srl 9 | Corsinvest Srl 10 | 11 | -------------------------------------------------------------------------------- /Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | ..\nupkgs\ 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Corsinvest.ProxmoxVE.AutoSnap.Api/ResultSnapVm.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: GPL-3.0-only 3 | * SPDX-FileCopyrightText: Copyright Corsinvest Srl 4 | */ 5 | 6 | namespace Corsinvest.ProxmoxVE.AutoSnap.Api; 7 | 8 | /// 9 | /// Execution Vm 10 | /// 11 | public class ResultSnapVm : ResultBaseSnap 12 | { 13 | /// 14 | /// Vm id 15 | /// 16 | /// 17 | public long VmId { get; internal set; } 18 | } -------------------------------------------------------------------------------- /3rd-party-licenses.md: -------------------------------------------------------------------------------- 1 | # License overview of included 3rd party libraries 2 | 3 | The project is licensed under the terms of the [LICENSE.md](LICENSE.md) 4 | 5 | However, includes several third-party Open-Source libraries, which are licensed under their own respective Open-Source licenses. 6 | 7 | ## Libraries directly included 8 | 9 | [Corsinvest ProxmoxVE Api](https://github.com/Corsinvest/cv4pve-api-dotnet) 10 | License: GPLv3 11 | 12 | [Corsinvest ProxmoxVE Api Extension](https://github.com/Corsinvest/cv4pve-api-dotnet) 13 | License: GPLv3 14 | 15 | [Dotnet Core](https://github.com/dotnet/core) 16 | License: MIT 17 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | publish: 10 | uses: Corsinvest/.github/.github/workflows/cv4pve-cli-publish.yml@main 11 | secrets: 12 | NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} 13 | WINGET_TOKEN: ${{ secrets.WINGET_TOKEN }} 14 | with: 15 | project-name: cv4pve-autosnap 16 | project-path: src/Corsinvest.ProxmoxVE.AutoSnap/Corsinvest.ProxmoxVE.AutoSnap.csproj 17 | nuget-project-path: src/Corsinvest.ProxmoxVE.AutoSnap.Api/Corsinvest.ProxmoxVE.AutoSnap.Api.csproj 18 | winget-id: Corsinvest.cv4pve.autosnap 19 | -------------------------------------------------------------------------------- /src/Corsinvest.ProxmoxVE.AutoSnap/Program.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: GPL-3.0-only 3 | * SPDX-FileCopyrightText: Copyright Corsinvest Srl 4 | */ 5 | 6 | using Corsinvest.ProxmoxVE.Api.Console.Helpers; 7 | using Corsinvest.ProxmoxVE.AutoSnap; 8 | using Corsinvest.ProxmoxVE.AutoSnap.Api; 9 | using Microsoft.Extensions.Logging; 10 | 11 | var app = ConsoleHelper.CreateApp(Application.Name, "Automatic snapshot VM/CT with retention"); 12 | var loggerFactory = ConsoleHelper.CreateLoggerFactory(app.GetLogLevelFromDebug()); 13 | 14 | _ = new Commands(app, loggerFactory); 15 | return await app.ExecuteAppAsync(args, loggerFactory.CreateLogger()); -------------------------------------------------------------------------------- /src/Corsinvest.ProxmoxVE.AutoSnap.Api/ResultBaseSnap.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: GPL-3.0-only 3 | * SPDX-FileCopyrightText: Copyright Corsinvest Srl 4 | */ 5 | 6 | using System.Diagnostics; 7 | 8 | namespace Corsinvest.ProxmoxVE.AutoSnap.Api; 9 | 10 | /// 11 | /// ResultBase 12 | /// 13 | public class ResultBaseSnap 14 | { 15 | private readonly Stopwatch _execution = new(); 16 | 17 | internal void Start() => _execution.Start(); 18 | internal void Stop() => _execution.Stop(); 19 | 20 | /// 21 | /// Elapsed 22 | /// 23 | public TimeSpan Elapsed => _execution.Elapsed; 24 | 25 | /// 26 | /// Status 27 | /// 28 | /// 29 | public virtual bool Status { get; internal set; } 30 | } -------------------------------------------------------------------------------- /src/Corsinvest.ProxmoxVE.AutoSnap.Api/ResultSnap.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: GPL-3.0-only 3 | * SPDX-FileCopyrightText: Copyright Corsinvest Srl 4 | */ 5 | 6 | namespace Corsinvest.ProxmoxVE.AutoSnap.Api; 7 | 8 | /// 9 | /// Execution Snap 10 | /// 11 | public class ResultSnap : ResultBaseSnap 12 | { 13 | /// 14 | /// Vms 15 | /// 16 | public List Vms { get; } = new List(); 17 | 18 | /// 19 | /// Status 20 | /// 21 | /// 22 | public override bool Status 23 | { 24 | get => !Vms.Any(a => !a.Status); 25 | internal set { } 26 | } 27 | 28 | /// 29 | /// Name of the snapshot 30 | /// 31 | public string SnapName { get; internal set; } = default!; 32 | } -------------------------------------------------------------------------------- /src/Corsinvest.ProxmoxVE.AutoSnap/Corsinvest.ProxmoxVE.AutoSnap.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net10.0 5 | cv4pve-autosnap 6 | Corsinvest for Proxmox VE Auto Snapshot 7 | Corsinvest for Proxmox VE Auto Snapshot 8 | Corsinvest for Proxmox VE Auto Snapshot 9 | true 10 | false 11 | https://github.com/Corsinvest/cv4pve-autosnap 12 | git 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ "master", "dev" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | workflow_dispatch: 9 | 10 | env: 11 | DOTNET_VERSION: '10.0.x' 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | 22 | - name: Setup .NET 23 | uses: actions/setup-dotnet@v4 24 | with: 25 | dotnet-version: ${{ env.DOTNET_VERSION }} 26 | 27 | - name: Restore dependencies 28 | run: dotnet restore 29 | 30 | - name: Build 31 | run: dotnet build --no-restore --configuration Release 32 | 33 | - name: Test 34 | run: dotnet test --no-build --configuration Release --verbosity normal 35 | 36 | - name: Upload Build Artifacts 37 | uses: actions/upload-artifact@v4 38 | with: 39 | name: build-artifacts 40 | path: src/**/bin/Release/ 41 | retention-days: 7 -------------------------------------------------------------------------------- /manifests/c/Corsinvest/cv4pve/autosnap/1.17.0/Corsinvest.cv4pve.autosnap.installer.yaml: -------------------------------------------------------------------------------- 1 | # Created using wingetcreate 1.10.3.0 2 | # yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.10.0.schema.json 3 | 4 | PackageIdentifier: Corsinvest.cv4pve.autosnap 5 | PackageVersion: 1.17.0 6 | InstallerType: zip 7 | NestedInstallerType: portable 8 | NestedInstallerFiles: 9 | - RelativeFilePath: cv4pve-autosnap.exe 10 | PortableCommandAlias: cv4pve-autosnap 11 | ReleaseDate: 2025-12-10 12 | Installers: 13 | - Architecture: x64 14 | InstallerUrl: https://github.com/Corsinvest/cv4pve-autosnap/releases/download/v1.17.0/cv4pve-autosnap.exe-win-x64.zip 15 | InstallerSha256: 7024451885CAE4C8B53237DD0C530DE152D2A28A3DAF6F3FA932669B35480373 16 | - Architecture: x86 17 | InstallerUrl: https://github.com/Corsinvest/cv4pve-autosnap/releases/download/v1.17.0/cv4pve-autosnap.exe-win-x86.zip 18 | InstallerSha256: 54A42B1416714663DB1281E8C8C2AB254E45527E002527B3DFC966E0652D1194 19 | - Architecture: arm64 20 | InstallerUrl: https://github.com/Corsinvest/cv4pve-autosnap/releases/download/v1.17.0/cv4pve-autosnap.exe-win-arm64.zip 21 | InstallerSha256: C62128FBF55DDC628837A6B4D4A56A1145D037F63D07D79B651F8B9226299D45 22 | ManifestType: installer 23 | ManifestVersion: 1.10.0 24 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | // Usare IntelliSense per individuare gli attributi esistenti per il debug C# 6 | // Usa il passaggio del mouse per la descrizione degli attributi esistenti 7 | // Per ulteriori informazioni, visitare https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // Se i framework di destinazione sono stati modificati, assicurarsi di aggiornare il percorso del programma. 13 | "program": "${workspaceFolder}/src/Corsinvest.ProxmoxVE.AutoSnap/bin/Debug/net10.0/cv4pve-autosnap.dll", 14 | "args": [ 15 | "@Parm.parm" 16 | ], 17 | "cwd": "${workspaceFolder}/src/Corsinvest.ProxmoxVE.AutoSnap", 18 | // Per ulteriori informazioni sul campo 'console', vedere https://aka.ms/VSCode-CS-LaunchJson-Console 19 | "console": "internalConsole", 20 | "stopAtEntry": false 21 | }, 22 | { 23 | "name": ".NET Core Attach", 24 | "type": "coreclr", 25 | "request": "attach" 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /src/Corsinvest.ProxmoxVE.AutoSnap.Api/HookPhase.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: GPL-3.0-only 3 | * SPDX-FileCopyrightText: Copyright Corsinvest Srl 4 | */ 5 | 6 | namespace Corsinvest.ProxmoxVE.AutoSnap.Api; 7 | 8 | /// 9 | /// Hook phase 10 | /// 11 | public enum HookPhase 12 | { 13 | /// 14 | /// Clean Job Start 15 | /// 16 | CleanJobStart = 0, 17 | 18 | /// 19 | /// Clean Job End 20 | /// 21 | CleanJobEnd = 1, 22 | 23 | /// 24 | /// Snap Job Start 25 | /// 26 | SnapJobStart = 2, 27 | 28 | /// 29 | /// Snap Job End 30 | /// 31 | SnapJobEnd = 3, 32 | 33 | /// 34 | /// Snap Create Pre 35 | /// 36 | SnapCreatePre = 4, 37 | 38 | /// 39 | /// Snap Create Post 40 | /// 41 | SnapCreatePost = 5, 42 | 43 | /// 44 | /// Snap Create Abort 45 | /// 46 | SnapCreateAbort = 6, 47 | 48 | /// 49 | /// Snap Remove Pre 50 | /// 51 | SnapRemovePre = 7, 52 | 53 | /// 54 | /// Snap Remove Post 55 | /// 56 | SnapRemovePost = 8, 57 | 58 | /// 59 | /// Snap Remove Abort 60 | /// 61 | SnapRemoveAbort = 9 62 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/Corsinvest.ProxmoxVE.AutoSnap.sln", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary;ForceNoAlign" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/Corsinvest.ProxmoxVE.AutoSnap.sln", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary;ForceNoAlign" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "--project", 36 | "${workspaceFolder}/Corsinvest.ProxmoxVE.AutoSnap.sln" 37 | ], 38 | "problemMatcher": "$msCompile" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /src/Corsinvest.ProxmoxVE.AutoSnap.Api/Corsinvest.ProxmoxVE.AutoSnap.Api.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0;net9.0;net10.0 4 | Library 5 | 6 | https://github.com/Corsinvest/cv4pve-autosnap 7 | icon.png 8 | README.md 9 | ProxmoxVE;Api,Client;Rest;Corsinvest;AutoSnap 10 | GPL-3.0-only 11 | ..\..\..\.nupkgs\ 12 | git 13 | https://github.com/Corsinvest/cv4pve-autosnap 14 | true 15 | true 16 | True 17 | 18 | 19 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 20 | 21 | true 22 | snupkg 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /.github/workflows/quality.yml: -------------------------------------------------------------------------------- 1 | name: Quality 2 | 3 | on: 4 | push: 5 | branches: [ "master", "dev" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | workflow_dispatch: 9 | 10 | env: 11 | DOTNET_VERSION: '10.0.x' 12 | 13 | jobs: 14 | analyze: 15 | name: Analyze 16 | runs-on: ubuntu-latest 17 | permissions: 18 | actions: read 19 | contents: read 20 | security-events: write 21 | 22 | steps: 23 | - name: Checkout repository 24 | uses: actions/checkout@v4 25 | with: 26 | fetch-depth: 0 27 | 28 | - name: Initialize CodeQL 29 | uses: github/codeql-action/init@v3 30 | with: 31 | languages: 'csharp' 32 | 33 | - name: Setup .NET 34 | uses: actions/setup-dotnet@v4 35 | with: 36 | dotnet-version: ${{ env.DOTNET_VERSION }} 37 | 38 | - name: Restore dependencies 39 | run: dotnet restore 40 | 41 | - name: Build 42 | run: dotnet build --no-restore --configuration Release 43 | 44 | - name: Perform CodeQL Analysis 45 | uses: github/codeql-action/analyze@v3 46 | 47 | lint: 48 | name: Lint 49 | runs-on: ubuntu-latest 50 | 51 | steps: 52 | - name: Checkout repository 53 | uses: actions/checkout@v4 54 | with: 55 | fetch-depth: 0 56 | 57 | - name: Setup .NET 58 | uses: actions/setup-dotnet@v4 59 | with: 60 | dotnet-version: ${{ env.DOTNET_VERSION }} 61 | 62 | - name: Restore dependencies 63 | run: dotnet restore 64 | 65 | - name: Lint with dotnet format 66 | run: | 67 | dotnet format --verify-no-changes --severity error -------------------------------------------------------------------------------- /manifests/c/Corsinvest/cv4pve/autosnap/1.17.0/Corsinvest.cv4pve.autosnap.locale.en-US.yaml: -------------------------------------------------------------------------------- 1 | # Created using wingetcreate 1.10.3.0 2 | # yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.1.10.0.schema.json 3 | 4 | PackageIdentifier: Corsinvest.cv4pve.autosnap 5 | PackageVersion: 1.17.0 6 | PackageLocale: en-US 7 | Publisher: Corsinvest Srl 8 | PublisherUrl: https://www.corsinvest.it 9 | PublisherSupportUrl: https://www.corsinvest.it/cv4pve 10 | PrivacyUrl: https://www.corsinvest.it/privacy 11 | Author: Corsinvest Srl 12 | PackageName: cv4pve-autosnap 13 | PackageUrl: https://github.com/Corsinvest/cv4pve-autosnap 14 | License: GPL-3.0-only 15 | LicenseUrl: https://github.com/Corsinvest/cv4pve-autosnap/blob/master/LICENSE.md 16 | Copyright: Copyright (c) 2019-2025 Corsinvest Srl 17 | CopyrightUrl: https://github.com/Corsinvest/cv4pve-autosnap/blob/master/LICENSE.md 18 | ShortDescription: Automatic Snapshot Tool for Proxmox VE 19 | Description: |- 20 | cv4pve-autosnap is an automatic snapshot tool for Proxmox VE virtual machines and containers. 21 | It creates snapshots with retention policies and supports flexible targeting patterns including 22 | ID/name based selection, bulk operations, and pool-based selections. 23 | 24 | Features: 25 | - Automatic snapshot creation with retention policies 26 | - Support for VMs and Containers 27 | - Flexible VM selection (ID, name, patterns, pools) 28 | - Hook script execution (pre/post snapshot) 29 | - Multiple snapshot labels for different schedules 30 | - Clean command to remove old snapshots 31 | - Status command to view existing snapshots 32 | Moniker: cv4pve-autosnap 33 | Tags: 34 | - automation 35 | - backup 36 | - container 37 | - corsinvest 38 | - proxmox 39 | - proxmoxve 40 | - snapshot 41 | - virtualization 42 | - vm 43 | ReleaseNotesUrl: https://github.com/Corsinvest/cv4pve-autosnap/releases/tag/v1.17.0 44 | ManifestType: defaultLocale 45 | ManifestVersion: 1.10.0 46 | -------------------------------------------------------------------------------- /src/Corsinvest.ProxmoxVE.AutoSnap.Api/PhaseEventArgs.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: GPL-3.0-only 3 | * SPDX-FileCopyrightText: Copyright Corsinvest Srl 4 | */ 5 | 6 | using System.Globalization; 7 | using Corsinvest.ProxmoxVE.Api.Shared.Models.Cluster; 8 | 9 | namespace Corsinvest.ProxmoxVE.AutoSnap.Api; 10 | 11 | /// 12 | /// Phase event arguments 13 | /// 14 | /// 15 | /// Constructor 16 | /// 17 | /// 18 | /// 19 | /// 20 | /// 21 | /// 22 | /// 23 | /// 24 | /// 25 | public record PhaseEventArgs(HookPhase Phase, 26 | IClusterResourceVm Vm, 27 | string Label, 28 | int Keep, 29 | string SnapName, 30 | bool VmState, 31 | double Duration, 32 | bool Status) 33 | { 34 | /// 35 | /// Environments 36 | /// 37 | /// 38 | public IReadOnlyDictionary Environments 39 | => new Dictionary 40 | { 41 | {"CV4PVE_AUTOSNAP_PHASE", Application.PhaseEnumToStr(Phase)}, 42 | {"CV4PVE_AUTOSNAP_VMID", Vm.VmId + string.Empty }, 43 | {"CV4PVE_AUTOSNAP_VMNAME", Vm.Name }, 44 | {"CV4PVE_AUTOSNAP_VMTYPE", Vm.Type + string.Empty }, 45 | {"CV4PVE_AUTOSNAP_LABEL", Label }, 46 | {"CV4PVE_AUTOSNAP_KEEP", Keep + string.Empty }, 47 | {"CV4PVE_AUTOSNAP_SNAP_NAME", SnapName }, 48 | {"CV4PVE_AUTOSNAP_VMSTATE", VmState ? "1" : "0" }, 49 | {"CV4PVE_AUTOSNAP_DURATION", (Duration + string.Empty).Replace(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator,".") }, 50 | {"CV4PVE_AUTOSNAP_STATE", Status ? "1" : "0" }, 51 | }; 52 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐛 Bug Report 2 | description: Report a bug in cv4pve-autosnap 3 | title: "[BUG] " 4 | labels: ["bug", "needs investigation"] 5 | 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | ## 🐛 Bug Report 11 | 12 | Thanks for taking the time to fill out this bug report! 13 | 14 | **Before submitting:** Search existing issues to avoid duplicates. 15 | 16 | - type: textarea 17 | id: what-happened 18 | attributes: 19 | label: What happened? 20 | description: Describe the issue briefly 21 | placeholder: Describe your issue! 22 | validations: 23 | required: true 24 | 25 | - type: textarea 26 | id: expected-behavior 27 | attributes: 28 | label: Expected behavior 29 | description: What you expected to happen 30 | placeholder: Describe the expected behavior! 31 | validations: 32 | required: true 33 | 34 | - type: textarea 35 | id: command-used 36 | attributes: 37 | label: Command used 38 | description: | 39 | Paste the exact command you ran. **Remove passwords and API tokens!** 40 | placeholder: | 41 | cv4pve-autosnap --host=pve.local --username=user@pam --password=*** --vmid=100 snap --label=daily --keep=7 42 | render: bash 43 | validations: 44 | required: true 45 | 46 | - type: textarea 47 | id: logs 48 | attributes: 49 | label: Log output 50 | description: | 51 | **Use `--debug` parameter to get detailed logs, then paste the output here.** 52 | 53 | ⚠️ **ATTENTION: Remove sensitive data (passwords, API keys) before pasting!** 54 | placeholder: Run your command with --debug and paste the output here 55 | render: shell 56 | validations: 57 | required: true 58 | 59 | - type: input 60 | id: cv4pve-version 61 | attributes: 62 | label: cv4pve-autosnap Version 63 | placeholder: "v1.14.10" 64 | validations: 65 | required: true 66 | 67 | - type: input 68 | id: proxmox-version 69 | attributes: 70 | label: Proxmox VE Version 71 | placeholder: "8.0.4" 72 | validations: 73 | required: true 74 | 75 | - type: input 76 | id: working-version 77 | attributes: 78 | label: Last working version 79 | description: Did it work before? Which version? 80 | placeholder: "v1.14.8 or 'Never worked'" 81 | 82 | - type: dropdown 83 | id: os 84 | attributes: 85 | label: Operating System 86 | options: 87 | - Linux 88 | - Windows 89 | - macOS 90 | - Docker 91 | - Other 92 | validations: 93 | required: true 94 | 95 | - type: checkboxes 96 | id: pr 97 | attributes: 98 | label: Pull Request 99 | options: 100 | - label: I would like to do a Pull Request 101 | -------------------------------------------------------------------------------- /hooks/hook-template.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | REM SPDX-License-Identifier: GPL-3.0-only 3 | REM SPDX-FileCopyrightText: Copyright Corsinvest Srl 4 | REM 5 | REM Corsinvest automatic snapshot for Proxmox VE cv4pve-autosnap hook script. 6 | REM Process environment variables as received from and set by cv4pve-autosnap. 7 | 8 | ECHO ---------------------------------------------------------- 9 | ECHO CV4PVE_AUTOSNAP_PHASE: %CV4PVE_AUTOSNAP_PHASE% 10 | ECHO CV4PVE_AUTOSNAP_VMID: %CV4PVE_AUTOSNAP_VMID% 11 | ECHO CV4PVE_AUTOSNAP_VMNAME: %CV4PVE_AUTOSNAP_VMNAME% 12 | ECHO CV4PVE_AUTOSNAP_VMTYPE: %CV4PVE_AUTOSNAP_VMTYPE% 13 | ECHO CV4PVE_AUTOSNAP_LABEL: %CV4PVE_AUTOSNAP_LABEL% 14 | ECHO CV4PVE_AUTOSNAP_KEEP: %CV4PVE_AUTOSNAP_KEEP% 15 | ECHO CV4PVE_AUTOSNAP_SNAP_NAME: %CV4PVE_AUTOSNAP_SNAP_NAME% 16 | ECHO CV4PVE_AUTOSNAP_VMSTATE: %CV4PVE_AUTOSNAP_VMSTATE% 17 | ECHO CV4PVE_AUTOSNAP_DURATION: %CV4PVE_AUTOSNAP_DURATION% 18 | ECHO CV4PVE_AUTOSNAP_STATE: %CV4PVE_AUTOSNAP_STATE% 19 | ECHO CV4PVE_AUTOSNAP_DEBUG: %CV4PVE_AUTOSNAP_DEBUG% 20 | ECHO CV4PVE_AUTOSNAP_DRY_RUN: %CV4PVE_AUTOSNAP_DRY_RUN% 21 | ECHO ---------------------------------------------------------- 22 | 23 | REM Hook script phases: 24 | REM clean-job-start - Before starting cleanup operation 25 | REM clean-job-end - After finishing cleanup operation 26 | REM snap-job-start - Before starting snapshot operation 27 | REM snap-job-end - After finishing snapshot operation 28 | REM snap-create-pre - Before creating snapshot 29 | REM snap-create-post - After successfully creating snapshot 30 | REM snap-create-abort - When snapshot creation is aborted 31 | REM snap-remove-pre - Before removing snapshot 32 | REM snap-remove-post - After successfully removing snapshot 33 | REM snap-remove-abort - When snapshot removal is aborted 34 | 35 | IF "%CV4PVE_AUTOSNAP_PHASE%"=="clean-job-start" ( 36 | REM Add custom logic here for cleanup job start 37 | ) ELSE IF "%CV4PVE_AUTOSNAP_PHASE%"=="clean-job-end" ( 38 | REM Add custom logic here for cleanup job end 39 | ) ELSE IF "%CV4PVE_AUTOSNAP_PHASE%"=="snap-job-start" ( 40 | REM Add custom logic here for snapshot job start 41 | ) ELSE IF "%CV4PVE_AUTOSNAP_PHASE%"=="snap-job-end" ( 42 | REM Add custom logic here for snapshot job end 43 | ) ELSE IF "%CV4PVE_AUTOSNAP_PHASE%"=="snap-create-pre" ( 44 | REM Add custom logic here for before snapshot creation 45 | ) ELSE IF "%CV4PVE_AUTOSNAP_PHASE%"=="snap-create-post" ( 46 | REM Add custom logic here for after successful snapshot creation 47 | ) ELSE IF "%CV4PVE_AUTOSNAP_PHASE%"=="snap-create-abort" ( 48 | REM Add custom logic here for when snapshot creation is aborted 49 | ) ELSE IF "%CV4PVE_AUTOSNAP_PHASE%"=="snap-remove-pre" ( 50 | REM Add custom logic here for before snapshot removal 51 | ) ELSE IF "%CV4PVE_AUTOSNAP_PHASE%"=="snap-remove-post" ( 52 | REM Add custom logic here for after successful snapshot removal 53 | ) ELSE IF "%CV4PVE_AUTOSNAP_PHASE%"=="snap-remove-abort" ( 54 | REM Add custom logic here for when snapshot removal is aborted 55 | ) ELSE ( 56 | ECHO "unknown phase '%CV4PVE_AUTOSNAP_PHASE%'" 57 | EXIT 1 58 | ) -------------------------------------------------------------------------------- /hooks/hook-template.ps1: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: GPL-3.0-only 2 | # SPDX-FileCopyrightText: Copyright Corsinvest Srl 3 | # 4 | # Corsinvest automatic snapshot for Proxmox VE cv4pve-autosnap hook script. 5 | # Process environment variables as received from and set by cv4pve-autosnap. 6 | 7 | # Display all environment variables for debugging 8 | Write-Host "----------------------------------------------------------" 9 | Write-Host "CV4PVE_AUTOSNAP_PHASE: " $env:CV4PVE_AUTOSNAP_PHASE 10 | Write-Host "CV4PVE_AUTOSNAP_VMID: " $env:CV4PVE_AUTOSNAP_VMID 11 | Write-Host "CV4PVE_AUTOSNAP_VMNAME: " $env:CV4PVE_AUTOSNAP_VMNAME 12 | Write-Host "CV4PVE_AUTOSNAP_VMTYPE: " $env:CV4PVE_AUTOSNAP_VMTYPE 13 | Write-Host "CV4PVE_AUTOSNAP_LABEL: " $env:CV4PVE_AUTOSNAP_LABEL 14 | Write-Host "CV4PVE_AUTOSNAP_KEEP: " $env:CV4PVE_AUTOSNAP_KEEP 15 | Write-Host "CV4PVE_AUTOSNAP_SNAP_NAME: " $env:CV4PVE_AUTOSNAP_SNAP_NAME 16 | Write-Host "CV4PVE_AUTOSNAP_VMSTATE: " $env:CV4PVE_AUTOSNAP_VMSTATE 17 | Write-Host "CV4PVE_AUTOSNAP_DURATION: " $env:CV4PVE_AUTOSNAP_DURATION 18 | Write-Host "CV4PVE_AUTOSNAP_STATE: " $env:CV4PVE_AUTOSNAP_STATE 19 | Write-Host "CV4PVE_AUTOSNAP_DEBUG: " $env:CV4PVE_AUTOSNAP_DEBUG 20 | Write-Host "CV4PVE_AUTOSNAP_DRY_RUN: " $env:CV4PVE_AUTOSNAP_DRY_RUN 21 | Write-Host "----------------------------------------------------------" 22 | 23 | # Hook script phases: 24 | # clean-job-start - Before starting cleanup operation 25 | # clean-job-end - After finishing cleanup operation 26 | # snap-job-start - Before starting snapshot operation 27 | # snap-job-end - After finishing snapshot operation 28 | # snap-create-pre - Before creating snapshot 29 | # snap-create-post - After successfully creating snapshot 30 | # snap-create-abort - When snapshot creation is aborted 31 | # snap-remove-pre - Before removing snapshot 32 | # snap-remove-post - After successfully removing snapshot 33 | # snap-remove-abort - When snapshot removal is aborted 34 | 35 | # Main hook logic based on phase 36 | switch ($env:CV4PVE_AUTOSNAP_PHASE) { 37 | # Clean job phases 38 | "clean-job-start" { 39 | # Add custom logic here for cleanup job start 40 | } 41 | "clean-job-end" { 42 | # Add custom logic here for cleanup job end 43 | } 44 | 45 | # Snap job phases 46 | "snap-job-start" { 47 | # Add custom logic here for snapshot job start 48 | } 49 | "snap-job-end" { 50 | # Add custom logic here for snapshot job end 51 | } 52 | 53 | # Snapshot creation phases 54 | "snap-create-pre" { 55 | # Add custom logic here for before snapshot creation 56 | } 57 | "snap-create-post" { 58 | # Add custom logic here for after successful snapshot creation 59 | } 60 | "snap-create-abort" { 61 | # Add custom logic here for when snapshot creation is aborted 62 | } 63 | 64 | # Snapshot removal phases 65 | "snap-remove-pre" { 66 | # Add custom logic here for before snapshot removal 67 | } 68 | "snap-remove-post" { 69 | # Add custom logic here for after successful snapshot removal 70 | } 71 | "snap-remove-abort" { 72 | # Add custom logic here for when snapshot removal is aborted 73 | } 74 | 75 | default { 76 | Write-Error "Unknown phase '$($env:CV4PVE_AUTOSNAP_PHASE)'" 77 | exit 1 78 | } 79 | } -------------------------------------------------------------------------------- /hooks/hook-template.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: GPL-3.0-only 3 | # SPDX-FileCopyrightText: Copyright Corsinvest Srl 4 | # 5 | # Corsinvest automatic snapshot for Proxmox VE cv4pve-autosnap hook script. 6 | # Process environment variables as received from and set by cv4pve-autosnap. 7 | 8 | hook() { 9 | echo "----------------------------------------------------------" 10 | echo "CV4PVE_AUTOSNAP_PHASE: $CV4PVE_AUTOSNAP_PHASE" 11 | echo "CV4PVE_AUTOSNAP_VMID: $CV4PVE_AUTOSNAP_VMID" 12 | echo "CV4PVE_AUTOSNAP_VMNAME: $CV4PVE_AUTOSNAP_VMNAME" 13 | echo "CV4PVE_AUTOSNAP_VMTYPE: $CV4PVE_AUTOSNAP_VMTYPE" 14 | echo "CV4PVE_AUTOSNAP_LABEL: $CV4PVE_AUTOSNAP_LABEL" 15 | echo "CV4PVE_AUTOSNAP_KEEP: $CV4PVE_AUTOSNAP_KEEP" 16 | echo "CV4PVE_AUTOSNAP_SNAP_NAME: $CV4PVE_AUTOSNAP_SNAP_NAME" 17 | echo "CV4PVE_AUTOSNAP_VMSTATE: $CV4PVE_AUTOSNAP_VMSTATE" 18 | echo "CV4PVE_AUTOSNAP_DURATION: $CV4PVE_AUTOSNAP_DURATION" 19 | echo "CV4PVE_AUTOSNAP_STATE: $CV4PVE_AUTOSNAP_STATE" 20 | echo "CV4PVE_AUTOSNAP_DEBUG: $CV4PVE_AUTOSNAP_DEBUG" 21 | echo "CV4PVE_AUTOSNAP_DRY_RUN: $CV4PVE_AUTOSNAP_DRY_RUN" 22 | echo "----------------------------------------------------------" 23 | 24 | # Hook script phases: 25 | # clean-job-start - Before starting cleanup operation 26 | # clean-job-end - After finishing cleanup operation 27 | # snap-job-start - Before starting snapshot operation 28 | # snap-job-end - After finishing snapshot operation 29 | # snap-create-pre - Before creating snapshot 30 | # snap-create-post - After successfully creating snapshot 31 | # snap-create-abort - When snapshot creation is aborted 32 | # snap-remove-pre - Before removing snapshot 33 | # snap-remove-post - After successfully removing snapshot 34 | # snap-remove-abort - When snapshot removal is aborted 35 | 36 | case "$CV4PVE_AUTOSNAP_PHASE" in 37 | # Clean job phases 38 | clean-job-start) 39 | # Add custom logic here for cleanup job start 40 | ;; 41 | clean-job-end) 42 | # Add custom logic here for cleanup job end 43 | ;; 44 | 45 | # Snap job phases 46 | snap-job-start) 47 | # Add custom logic here for snapshot job start 48 | ;; 49 | snap-job-end) 50 | # Add custom logic here for snapshot job end 51 | ;; 52 | 53 | # Snapshot creation phases 54 | snap-create-pre) 55 | # Add custom logic here for before snapshot creation 56 | ;; 57 | snap-create-post) 58 | # Add custom logic here for after successful snapshot creation 59 | ;; 60 | snap-create-abort) 61 | # Add custom logic here for when snapshot creation is aborted 62 | ;; 63 | 64 | # Snapshot removal phases 65 | snap-remove-pre) 66 | # Add custom logic here for before snapshot removal 67 | ;; 68 | snap-remove-post) 69 | # Add custom logic here for after successful snapshot removal 70 | ;; 71 | snap-remove-abort) 72 | # Add custom logic here for when snapshot removal is aborted 73 | ;; 74 | 75 | *) 76 | echo "unknown phase '$CV4PVE_AUTOSNAP_PHASE'" 77 | return 1 78 | ;; 79 | esac 80 | } 81 | 82 | hook -------------------------------------------------------------------------------- /Corsinvest.ProxmoxVE.AutoSnap.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.1.32210.238 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C6B0694C-362C-4BEE-8EBA-28ECF5780D57}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Corsinvest.ProxmoxVE.AutoSnap", "src\Corsinvest.ProxmoxVE.AutoSnap\Corsinvest.ProxmoxVE.AutoSnap.csproj", "{6D4D450B-25C7-4CC8-AFB5-3EFCBD32EC37}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Corsinvest.ProxmoxVE.AutoSnap.Api", "src\Corsinvest.ProxmoxVE.AutoSnap.Api\Corsinvest.ProxmoxVE.AutoSnap.Api.csproj", "{857DC686-6FB8-4FA6-8534-B062768152E3}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Debug|x64 = Debug|x64 16 | Debug|x86 = Debug|x86 17 | Release|Any CPU = Release|Any CPU 18 | Release|x64 = Release|x64 19 | Release|x86 = Release|x86 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {6D4D450B-25C7-4CC8-AFB5-3EFCBD32EC37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {6D4D450B-25C7-4CC8-AFB5-3EFCBD32EC37}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {6D4D450B-25C7-4CC8-AFB5-3EFCBD32EC37}.Debug|x64.ActiveCfg = Debug|Any CPU 25 | {6D4D450B-25C7-4CC8-AFB5-3EFCBD32EC37}.Debug|x64.Build.0 = Debug|Any CPU 26 | {6D4D450B-25C7-4CC8-AFB5-3EFCBD32EC37}.Debug|x86.ActiveCfg = Debug|Any CPU 27 | {6D4D450B-25C7-4CC8-AFB5-3EFCBD32EC37}.Debug|x86.Build.0 = Debug|Any CPU 28 | {6D4D450B-25C7-4CC8-AFB5-3EFCBD32EC37}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {6D4D450B-25C7-4CC8-AFB5-3EFCBD32EC37}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {6D4D450B-25C7-4CC8-AFB5-3EFCBD32EC37}.Release|x64.ActiveCfg = Release|Any CPU 31 | {6D4D450B-25C7-4CC8-AFB5-3EFCBD32EC37}.Release|x64.Build.0 = Release|Any CPU 32 | {6D4D450B-25C7-4CC8-AFB5-3EFCBD32EC37}.Release|x86.ActiveCfg = Release|Any CPU 33 | {6D4D450B-25C7-4CC8-AFB5-3EFCBD32EC37}.Release|x86.Build.0 = Release|Any CPU 34 | {857DC686-6FB8-4FA6-8534-B062768152E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {857DC686-6FB8-4FA6-8534-B062768152E3}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {857DC686-6FB8-4FA6-8534-B062768152E3}.Debug|x64.ActiveCfg = Debug|Any CPU 37 | {857DC686-6FB8-4FA6-8534-B062768152E3}.Debug|x64.Build.0 = Debug|Any CPU 38 | {857DC686-6FB8-4FA6-8534-B062768152E3}.Debug|x86.ActiveCfg = Debug|Any CPU 39 | {857DC686-6FB8-4FA6-8534-B062768152E3}.Debug|x86.Build.0 = Debug|Any CPU 40 | {857DC686-6FB8-4FA6-8534-B062768152E3}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {857DC686-6FB8-4FA6-8534-B062768152E3}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {857DC686-6FB8-4FA6-8534-B062768152E3}.Release|x64.ActiveCfg = Release|Any CPU 43 | {857DC686-6FB8-4FA6-8534-B062768152E3}.Release|x64.Build.0 = Release|Any CPU 44 | {857DC686-6FB8-4FA6-8534-B062768152E3}.Release|x86.ActiveCfg = Release|Any CPU 45 | {857DC686-6FB8-4FA6-8534-B062768152E3}.Release|x86.Build.0 = Release|Any CPU 46 | EndGlobalSection 47 | GlobalSection(SolutionProperties) = preSolution 48 | HideSolutionNode = FALSE 49 | EndGlobalSection 50 | GlobalSection(NestedProjects) = preSolution 51 | {6D4D450B-25C7-4CC8-AFB5-3EFCBD32EC37} = {C6B0694C-362C-4BEE-8EBA-28ECF5780D57} 52 | {857DC686-6FB8-4FA6-8534-B062768152E3} = {C6B0694C-362C-4BEE-8EBA-28ECF5780D57} 53 | EndGlobalSection 54 | GlobalSection(ExtensibilityGlobals) = postSolution 55 | SolutionGuid = {FF0CE8AE-82AF-4E55-B4CF-21752D47280F} 56 | EndGlobalSection 57 | EndGlobal 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | 33 | # Visual Studio 2015/2017 cache/options directory 34 | .vs/ 35 | # Uncomment if you have tasks that create the project's static files in wwwroot 36 | #wwwroot/ 37 | 38 | # Visual Studio 2017 auto generated files 39 | Generated\ Files/ 40 | 41 | # MSTest test Results 42 | [Tt]est[Rr]esult*/ 43 | [Bb]uild[Ll]og.* 44 | 45 | # NUnit 46 | *.VisualState.xml 47 | TestResult.xml 48 | nunit-*.xml 49 | 50 | # Build Results of an ATL Project 51 | [Dd]ebugPS/ 52 | [Rr]eleasePS/ 53 | dlldata.c 54 | 55 | # Benchmark Results 56 | BenchmarkDotNet.Artifacts/ 57 | 58 | # .NET Core 59 | project.lock.json 60 | project.fragment.lock.json 61 | artifacts/ 62 | 63 | # StyleCop 64 | StyleCopReport.xml 65 | 66 | # Files built by Visual Studio 67 | *_i.c 68 | *_p.c 69 | *_h.h 70 | *.ilk 71 | *.meta 72 | *.obj 73 | *.iobj 74 | *.pch 75 | *.pdb 76 | *.ipdb 77 | *.pgc 78 | *.pgd 79 | *.rsp 80 | *.sbr 81 | *.tlb 82 | *.tli 83 | *.tlh 84 | *.tmp 85 | *.tmp_proj 86 | *_wpftmp.csproj 87 | *.log 88 | *.vspscc 89 | *.vssscc 90 | .builds 91 | *.pidb 92 | *.svclog 93 | *.scc 94 | 95 | # Chutzpah Test files 96 | _Chutzpah* 97 | 98 | # Visual C++ cache files 99 | ipch/ 100 | *.aps 101 | *.ncb 102 | *.opendb 103 | *.opensdf 104 | *.sdf 105 | *.cachefile 106 | *.VC.db 107 | *.VC.VC.opendb 108 | 109 | # Visual Studio profiler 110 | *.psess 111 | *.vsp 112 | *.vspx 113 | *.sap 114 | 115 | # Visual Studio Trace Files 116 | *.e2e 117 | 118 | # TFS 2012 Local Workspace 119 | $tf/ 120 | 121 | # Guidance Automation Toolkit 122 | *.gpState 123 | 124 | # ReSharper is a .NET coding add-in 125 | _ReSharper*/ 126 | *.[Rr]e[Ss]harper 127 | *.DotSettings.user 128 | 129 | # JustCode is a .NET coding add-in 130 | .JustCode 131 | 132 | # TeamCity is a build add-in 133 | _TeamCity* 134 | 135 | # DotCover is a Code Coverage Tool 136 | *.dotCover 137 | 138 | # AxoCover is a Code Coverage Tool 139 | .axoCover/* 140 | !.axoCover/settings.json 141 | 142 | # Visual Studio code coverage results 143 | *.coverage 144 | *.coveragexml 145 | 146 | # NCrunch 147 | _NCrunch_* 148 | .*crunch*.local.xml 149 | nCrunchTemp_* 150 | 151 | # MightyMoose 152 | *.mm.* 153 | AutoTest.Net/ 154 | 155 | # Web workbench (sass) 156 | .sass-cache/ 157 | 158 | # Installshield output folder 159 | [Ee]xpress/ 160 | 161 | # DocProject is a documentation generator add-in 162 | DocProject/buildhelp/ 163 | DocProject/Help/*.HxT 164 | DocProject/Help/*.HxC 165 | DocProject/Help/*.hhc 166 | DocProject/Help/*.hhk 167 | DocProject/Help/*.hhp 168 | DocProject/Help/Html2 169 | DocProject/Help/html 170 | 171 | # Click-Once directory 172 | publish/ 173 | 174 | # Publish Web Output 175 | *.[Pp]ublish.xml 176 | *.azurePubxml 177 | # Note: Comment the next line if you want to checkin your web deploy settings, 178 | # but database connection strings (with potential passwords) will be unencrypted 179 | *.pubxml 180 | *.publishproj 181 | 182 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 183 | # checkin your Azure Web App publish settings, but sensitive information contained 184 | # in these scripts will be unencrypted 185 | PublishScripts/ 186 | 187 | # NuGet Packages 188 | *.nupkg 189 | # NuGet Symbol Packages 190 | *.snupkg 191 | # The packages folder can be ignored because of Package Restore 192 | **/[Pp]ackages/* 193 | # except build/, which is used as an MSBuild target. 194 | !**/[Pp]ackages/build/ 195 | # Uncomment if necessary however generally it will be regenerated when needed 196 | #!**/[Pp]ackages/repositories.config 197 | # NuGet v3's project.json files produces more ignorable files 198 | *.nuget.props 199 | *.nuget.targets 200 | 201 | # Microsoft Azure Build Output 202 | csx/ 203 | *.build.csdef 204 | 205 | # Microsoft Azure Emulator 206 | ecf/ 207 | rcf/ 208 | 209 | # Windows Store app package directories and files 210 | AppPackages/ 211 | BundleArtifacts/ 212 | Package.StoreAssociation.xml 213 | _pkginfo.txt 214 | *.appx 215 | *.appxbundle 216 | *.appxupload 217 | 218 | # Visual Studio cache files 219 | # files ending in .cache can be ignored 220 | *.[Cc]ache 221 | # but keep track of directories ending in .cache 222 | !?*.[Cc]ache/ 223 | 224 | # Others 225 | ClientBin/ 226 | ~$* 227 | *~ 228 | *.dbmdl 229 | *.dbproj.schemaview 230 | *.jfm 231 | *.pfx 232 | *.publishsettings 233 | orleans.codegen.cs 234 | 235 | # Including strong name files can present a security risk 236 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 237 | #*.snk 238 | 239 | # Since there are multiple workflows, uncomment next line to ignore bower_components 240 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 241 | #bower_components/ 242 | 243 | # RIA/Silverlight projects 244 | Generated_Code/ 245 | 246 | # Backup & report files from converting an old project file 247 | # to a newer Visual Studio version. Backup files are not needed, 248 | # because we have git ;-) 249 | _UpgradeReport_Files/ 250 | Backup*/ 251 | UpgradeLog*.XML 252 | UpgradeLog*.htm 253 | ServiceFabricBackup/ 254 | *.rptproj.bak 255 | 256 | # SQL Server files 257 | *.mdf 258 | *.ldf 259 | *.ndf 260 | 261 | # Business Intelligence projects 262 | *.rdl.data 263 | *.bim.layout 264 | *.bim_*.settings 265 | *.rptproj.rsuser 266 | *- [Bb]ackup.rdl 267 | *- [Bb]ackup ([0-9]).rdl 268 | *- [Bb]ackup ([0-9][0-9]).rdl 269 | 270 | # Microsoft Fakes 271 | FakesAssemblies/ 272 | 273 | # GhostDoc plugin setting file 274 | *.GhostDoc.xml 275 | 276 | # Node.js Tools for Visual Studio 277 | .ntvs_analysis.dat 278 | node_modules/ 279 | 280 | # Visual Studio 6 build log 281 | *.plg 282 | 283 | # Visual Studio 6 workspace options file 284 | *.opt 285 | 286 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 287 | *.vbw 288 | 289 | # Visual Studio LightSwitch build output 290 | **/*.HTMLClient/GeneratedArtifacts 291 | **/*.DesktopClient/GeneratedArtifacts 292 | **/*.DesktopClient/ModelManifest.xml 293 | **/*.Server/GeneratedArtifacts 294 | **/*.Server/ModelManifest.xml 295 | _Pvt_Extensions 296 | 297 | # Paket dependency manager 298 | .paket/paket.exe 299 | paket-files/ 300 | 301 | # FAKE - F# Make 302 | .fake/ 303 | 304 | # CodeRush personal settings 305 | .cr/personal 306 | 307 | # Python Tools for Visual Studio (PTVS) 308 | __pycache__/ 309 | *.pyc 310 | 311 | # Cake - Uncomment if you are using it 312 | # tools/** 313 | # !tools/packages.config 314 | 315 | # Tabs Studio 316 | *.tss 317 | 318 | # Telerik's JustMock configuration file 319 | *.jmconfig 320 | 321 | # BizTalk build output 322 | *.btp.cs 323 | *.btm.cs 324 | *.odx.cs 325 | *.xsd.cs 326 | 327 | # OpenCover UI analysis results 328 | OpenCover/ 329 | 330 | # Azure Stream Analytics local run output 331 | ASALocalRun/ 332 | 333 | # MSBuild Binary and Structured Log 334 | *.binlog 335 | 336 | # NVidia Nsight GPU debugger configuration file 337 | *.nvuser 338 | 339 | # MFractors (Xamarin productivity tool) working folder 340 | .mfractor/ 341 | 342 | # Local History for Visual Studio 343 | .localhistory/ 344 | 345 | # BeatPulse healthcheck temp database 346 | healthchecksdb 347 | 348 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 349 | MigrationBackup/ 350 | 351 | # Ionide (cross platform F# VS Code tools) working folder 352 | .ionide/ 353 | 354 | *.parm 355 | 356 | artifacts_exe/ 357 | dotnet-releaser.toml -------------------------------------------------------------------------------- /src/Corsinvest.ProxmoxVE.AutoSnap/Commands.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: GPL-3.0-only 3 | * SPDX-FileCopyrightText: Copyright Corsinvest Srl 4 | */ 5 | 6 | using System.CommandLine; 7 | using Corsinvest.ProxmoxVE.Api.Shared.Utils; 8 | using Corsinvest.ProxmoxVE.Api.Console.Helpers; 9 | using Corsinvest.ProxmoxVE.AutoSnap.Api; 10 | using Microsoft.Extensions.Logging; 11 | 12 | namespace Corsinvest.ProxmoxVE.AutoSnap; 13 | 14 | /// 15 | /// Console command. 16 | /// 17 | public class Commands 18 | { 19 | private string? _scriptHook; 20 | private readonly ILoggerFactory _loggerFactory; 21 | private readonly bool _dryRun; 22 | private readonly bool _debug; 23 | private readonly TextWriter _out; 24 | 25 | public Commands(RootCommand command, ILoggerFactory loggerFactory) 26 | { 27 | _dryRun = command.DryRunIsActive(); 28 | _debug = command.DebugIsActive(); 29 | _out = Console.Out; 30 | 31 | _loggerFactory = loggerFactory; 32 | 33 | var optVmIds = command.VmIdsOrNamesOption(); 34 | optVmIds.Required = true; 35 | 36 | var optTimeout = command.TimeoutOption(); 37 | optTimeout.DefaultValueFactory = (_) => 30L; 38 | 39 | var optTimestampFormat = command.AddOption("--timestamp-format", $"Specify different timestamp format"); 40 | optTimestampFormat.DefaultValueFactory = (_) => Application.DefaultTimestampFormat; 41 | 42 | var optMaxPercentageStorage = command.AddOption("--max-perc-storage", "Max percentage storage") 43 | .AddValidatorRange(1, 100); 44 | optMaxPercentageStorage.DefaultValueFactory = (_) => 95; 45 | 46 | Snap(command, optVmIds, optTimeout, optTimestampFormat, optMaxPercentageStorage); 47 | Clean(command, optVmIds, optTimeout, optTimestampFormat); 48 | Status(command, optVmIds, optTimestampFormat); 49 | } 50 | 51 | private async Task CreateAppAsync(RootCommand command) 52 | { 53 | var app = new Application(await command.ClientTryLoginAsync(_loggerFactory), _loggerFactory, _out, _dryRun); 54 | app.PhaseEvent += App_PhaseEvent; 55 | return app; 56 | } 57 | 58 | private void App_PhaseEvent(object? sender, PhaseEventArgs e) 59 | { 60 | if (_scriptHook == null || !File.Exists(_scriptHook)) { return; } 61 | 62 | var (stdOut, exitCode) = ShellHelper.Execute(_scriptHook, 63 | true, 64 | new Dictionary(e.Environments) 65 | { 66 | { "CV4PVE_AUTOSNAP_DEBUG", _debug ? "1" : "0" }, 67 | { "CV4PVE_AUTOSNAP_DRY_RUN", _dryRun ? "1" : "0" }, 68 | }, 69 | _out, 70 | _dryRun, 71 | _debug); 72 | 73 | if (exitCode != 0) { _out.WriteLine($"Script return code: {exitCode}"); } 74 | if (!string.IsNullOrWhiteSpace(stdOut)) { _out.Write(stdOut); } 75 | } 76 | 77 | private static Option OptionLabel(Command command) 78 | => command.AddOption("--label", "Is usually 'hourly', 'daily', 'weekly', or 'monthly'"); 79 | 80 | private static Option OptionKeep(Command command, int min = 1) 81 | { 82 | var opt = command.AddOption("--keep", "Specify the number which should will keep") 83 | .AddValidatorRange(min, 100); 84 | 85 | opt.Required = true; 86 | return opt; 87 | } 88 | 89 | private void Status(RootCommand command, Option optVmIds, Option optTimestampFormat) 90 | { 91 | var cmd = command.AddCommand("status", "Get list of all auto snapshots"); 92 | var optLabel = OptionLabel(cmd); 93 | var optOutput = cmd.TableOutputOption(); 94 | 95 | cmd.SetAction(async (parseResult) => 96 | { 97 | var app = await CreateAppAsync(command); 98 | var snapshots = await app.StatusAsync(parseResult.GetValue(optVmIds)!, 99 | parseResult.GetValue(optLabel)!, 100 | parseResult.GetValue(optTimestampFormat)!); 101 | if (snapshots.Any()) 102 | { 103 | var rows = new List(); 104 | foreach (var (vm, items) in snapshots) 105 | { 106 | rows.AddRange(items.Select(a => new object[] { vm.Node, 107 | vm.VmId, 108 | a.Date.ToString("yy/MM/dd HH:mm:ss"), 109 | a.Parent, 110 | a.Name, 111 | (a.Description + string.Empty).Replace("\n", string.Empty), 112 | a.VmStatus ? "X" : string.Empty })); 113 | } 114 | 115 | _out.Write(TableGenerator.To(["NODE", "VM", "TIME", "PARENT", "NAME", "DESCRIPTION", "VM STATUS"], 116 | rows, 117 | parseResult.GetValue(optOutput))); 118 | } 119 | }); 120 | } 121 | 122 | private void Clean(RootCommand command, 123 | Option optVmIds, 124 | Option optTimeout, 125 | Option optTimestampFormat) 126 | { 127 | var cmd = command.AddCommand("clean", "Remove auto snapshots"); 128 | var optLabel = OptionLabel(cmd); 129 | optLabel.Required = true; 130 | 131 | var optKeep = OptionKeep(cmd, 0); 132 | var optScript = cmd.ScriptFileOption(); 133 | 134 | cmd.SetAction(async (parseResult) => 135 | { 136 | _scriptHook = parseResult.GetValue(optScript); 137 | var app = await CreateAppAsync(command); 138 | return await app.CleanAsync(parseResult.GetValue(optVmIds)!, 139 | parseResult.GetValue(optLabel)!, 140 | parseResult.GetValue(optKeep), 141 | parseResult.GetValue(optTimeout) * 1000, 142 | parseResult.GetValue(optTimestampFormat)!) ? 0 : 1; 143 | }); 144 | } 145 | 146 | private void Snap(RootCommand command, 147 | Option optVmIds, 148 | Option optTimeout, 149 | Option optTimestampFormat, 150 | Option optMaxPercentageStorage) 151 | { 152 | var cmd = command.AddCommand("snap", "Will snap one time"); 153 | 154 | var optLabel = OptionLabel(cmd); 155 | optLabel.Required = true; 156 | 157 | var optKeep = OptionKeep(cmd); 158 | var optScript = cmd.ScriptFileOption(); 159 | var optState = cmd.AddOption("--state", "Save the vmstate (Include RAM)"); 160 | var optOnlyRunning = cmd.AddOption("--only-running", "Only VM/CT are running"); 161 | 162 | cmd.SetAction(async (parseResult) => 163 | { 164 | _scriptHook = parseResult.GetValue(optScript); 165 | var app = await CreateAppAsync(command); 166 | var snap = await app.SnapAsync(parseResult.GetValue(optVmIds)!, 167 | parseResult.GetValue(optLabel)!, 168 | parseResult.GetValue(optKeep), 169 | parseResult.GetValue(optState), 170 | parseResult.GetValue(optTimeout) * 1000, 171 | parseResult.GetValue(optTimestampFormat)!, 172 | parseResult.GetValue(optMaxPercentageStorage), 173 | parseResult.GetValue(optOnlyRunning)); 174 | 175 | return snap.Status ? 0 : 1; 176 | }); 177 | } 178 | } -------------------------------------------------------------------------------- /hooks/send-metrics.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # SPDX-License-Identifier: GPL-3.0-only 3 | # SPDX-FileCopyrightText: Copyright Corsinvest Srl 4 | # 5 | # Corsinvest automatic snapshot for Proxmox VE cv4pve-autosnap metric sender. 6 | # Sends snapshot metrics to external monitoring systems. 7 | # 8 | # Usage: 9 | # This script can be used as a hook script with cv4pve-autosnap to send 10 | # metrics about snapshot operations to various monitoring systems. 11 | # 12 | # Configuration: 13 | # The following environment variables can be used to configure the script: 14 | # 15 | # METRICS_ENDPOINT (default: http://localhost:9091/metrics/job/cv4pve-autosnap) 16 | # - The endpoint to send metrics to 17 | # 18 | # METRICS_TYPE (default: prometheus) 19 | # - Type of metrics format to send (prometheus, influxdb, json, grafana, loki) 20 | # 21 | # METRICS_USERNAME (optional) 22 | # - Username for basic authentication 23 | # 24 | # METRICS_PASSWORD (optional) 25 | # - Password for basic authentication 26 | # 27 | # Examples: 28 | # # Using as a script hook for Prometheus Pushgateway 29 | # cv4pve-autosnap --host=pve.local --api-token=token --vmid=100 \ 30 | # --script-hook=/path/to/send-metrics.sh snap --label=daily --keep=7 31 | # 32 | # # With custom endpoint and metrics type 33 | # METRICS_ENDPOINT="http://prometheus.local:9091/metrics/job/cv4pve-autosnap" \ 34 | # METRICS_TYPE="prometheus" \ 35 | # cv4pve-autosnap --host=pve.local --api-token=token --vmid=100 \ 36 | # --script-hook=/path/to/send-metrics.sh snap --label=daily --keep=7 37 | # 38 | # # For InfluxDB 39 | # METRICS_ENDPOINT="http://influxdb.local:8086/write?db=proxmox" \ 40 | # METRICS_TYPE="influxdb" \ 41 | # cv4pve-autosnap --host=pve.local --api-token=token --vmid=100 \ 42 | # --script-hook=/path/to/send-metrics.sh snap --label=daily --keep=7 43 | # 44 | # # For Grafana Loki 45 | # METRICS_ENDPOINT="http://loki.local:3100/loki/api/v1/push" \ 46 | # METRICS_TYPE="loki" \ 47 | # cv4pve-autosnap --host=pve.local --api-token=token --vmid=100 \ 48 | # --script-hook=/path/to/send-metrics.sh snap --label=daily --keep=7 49 | 50 | # Configuration - can be overridden by environment variables 51 | METRICS_ENDPOINT="${METRICS_ENDPOINT:-http://localhost:9091/metrics/job/cv4pve-autosnap}" 52 | METRICS_TYPE="${METRICS_TYPE:-prometheus}" # prometheus, influxdb, json, grafana 53 | METRICS_USERNAME="${METRICS_USERNAME:-}" # Optional basic auth 54 | METRICS_PASSWORD="${METRICS_PASSWORD:-}" # Optional basic auth 55 | 56 | # Function to send metrics via curl with optional authentication 57 | send_via_curl() { 58 | local payload="$1" 59 | local content_type="$2" 60 | local endpoint="$3" 61 | 62 | if [ -n "$METRICS_USERNAME" ] && [ -n "$METRICS_PASSWORD" ]; then 63 | curl -X POST \ 64 | -u "$METRICS_USERNAME:$METRICS_PASSWORD" \ 65 | -H "Content-Type: $content_type" \ 66 | --data "$payload" \ 67 | "$endpoint" 2>/dev/null 68 | else 69 | curl -X POST \ 70 | -H "Content-Type: $content_type" \ 71 | --data "$payload" \ 72 | "$endpoint" 2>/dev/null 73 | fi 74 | } 75 | 76 | # Function to send metrics via wget with optional authentication 77 | send_via_wget() { 78 | local payload="$1" 79 | local content_type="$2" 80 | local endpoint="$3" 81 | 82 | if [ -n "$METRICS_USERNAME" ] && [ -n "$METRICS_PASSWORD" ]; then 83 | wget --post-data="$payload" \ 84 | --header="Content-Type: $content_type" \ 85 | --user="$METRICS_USERNAME" \ 86 | --password="$METRICS_PASSWORD" \ 87 | "$endpoint" 2>/dev/null 88 | else 89 | wget --post-data="$payload" \ 90 | --header="Content-Type: $content_type" \ 91 | "$endpoint" 2>/dev/null 92 | fi 93 | } 94 | 95 | # Function to send metrics using available tool 96 | send_metrics_payload() { 97 | local payload="$1" 98 | local content_type="$2" 99 | local endpoint="$3" 100 | 101 | if command -v curl >/dev/null 2>&1; then 102 | send_via_curl "$payload" "$content_type" "$endpoint" 103 | elif command -v wget >/dev/null 2>&1; then 104 | send_via_wget "$payload" "$content_type" "$endpoint" 105 | else 106 | echo "No curl or wget available to send metrics" 107 | return 1 108 | fi 109 | } 110 | 111 | # Prometheus format 112 | send_prometheus_metrics() { 113 | local phase="$1" 114 | local vmid="$2" 115 | local vmname="$3" 116 | local vmtype="$4" 117 | local label="$5" 118 | local keep="$6" 119 | local snap_name="$7" 120 | local vmstate="$8" 121 | local duration="$9" 122 | local status="${10}" 123 | local timestamp="${11}" 124 | 125 | case "$phase" in 126 | snap-create-post) 127 | local metrics_payload="cv4pve_snapshot_duration_seconds{vmid=\"$vmid\",vmname=\"$vmname\",vmtype=\"$vmtype\",label=\"$label\",vmstate=\"$vmstate\",status=\"$status\"} $duration 128 | cv4pve_snapshot_created{vmid=\"$vmid\",vmname=\"$vmname\",vmtype=\"$vmtype\",label=\"$label\",snap_name=\"$snap_name\",status=\"$status\"} $timestamp" 129 | ;; 130 | clean-job-end) 131 | metrics_payload="cv4pve_snapshot_cleanup_total{vmid=\"$vmid\",vmname=\"$vmname\",label=\"$label\",status=\"$status\"} $timestamp" 132 | ;; 133 | *) 134 | return 0 # No metrics to send for other phases 135 | ;; 136 | esac 137 | 138 | send_metrics_payload "$metrics_payload" "text/plain" "$METRICS_ENDPOINT" 139 | } 140 | 141 | # InfluxDB format 142 | send_influxdb_metrics() { 143 | local phase="$1" 144 | local vmid="$2" 145 | local vmname="$3" 146 | local vmtype="$4" 147 | local label="$5" 148 | local keep="$6" 149 | local snap_name="$7" 150 | local vmstate="$8" 151 | local duration="$9" 152 | local status="${10}" 153 | local timestamp="${11}" 154 | 155 | case "$phase" in 156 | snap-create-post) 157 | local metrics_payload="snapshots,vmid=$vmid,vmname=$vmname,vmtype=$vmtype,label=$label,vmstate=$vmstate,status=$status duration=$duration,$timestamp" 158 | ;; 159 | clean-job-end) 160 | metrics_payload="cleanup,vmid=$vmid,vmname=$vmname,label=$label,status=$status count=1,$timestamp" 161 | ;; 162 | *) 163 | return 0 # No metrics to send for other phases 164 | ;; 165 | esac 166 | 167 | send_metrics_payload "$metrics_payload" "text/plain" "$METRICS_ENDPOINT" 168 | } 169 | 170 | # Generic JSON format 171 | send_json_metrics() { 172 | local phase="$1" 173 | local vmid="$2" 174 | local vmname="$3" 175 | local vmtype="$4" 176 | local label="$5" 177 | local keep="$6" 178 | local snap_name="$7" 179 | local vmstate="$8" 180 | local duration="$9" 181 | local status="${10}" 182 | local debug="${11}" 183 | local dry_run="${12}" 184 | local timestamp="${13}" 185 | 186 | local metrics_payload="{\"phase\":\"$phase\",\"vmid\":$vmid,\"vmname\":\"$vmname\",\"vmtype\":\"$vmtype\",\"label\":\"$label\",\"keep\":$keep,\"snap_name\":\"$snap_name\",\"vmstate\":\"$vmstate\",\"duration\":$duration,\"status\":\"$status\",\"debug\":\"$debug\",\"dry_run\":\"$dry_run\",\"timestamp\":$timestamp}" 187 | 188 | send_metrics_payload "$metrics_payload" "application/json" "$METRICS_ENDPOINT" 189 | } 190 | 191 | # Grafana Loki format 192 | send_loki_metrics() { 193 | local phase="$1" 194 | local vmid="$2" 195 | local vmname="$3" 196 | local vmtype="$4" 197 | local label="$5" 198 | local keep="$6" 199 | local snap_name="$7" 200 | local vmstate="$8" 201 | local duration="$9" 202 | local status="${10}" 203 | local debug="${11}" 204 | local dry_run="${12}" 205 | local timestamp="${13}" 206 | 207 | # Convert timestamp to nanoseconds for Loki 208 | local loki_timestamp=$(($timestamp * 1000000000)) 209 | 210 | local metrics_payload="{\"streams\":[{\"stream\":{\"job\":\"cv4pve-autosnap\",\"vmid\":\"$vmid\",\"phase\":\"$phase\"},\"values\":[[\"$loki_timestamp\",\"{\\\"vmid\\\":\\\"$vmid\\\",\\\"vmname\\\":\\\"$vmname\\\",\\\"vmtype\\\":\\\"$vmtype\\\",\\\"label\\\":\\\"$label\\\",\\\"phase\\\":\\\"$phase\\\",\\\"duration\\\":$duration,\\\"status\\\":\\\"$status\\\"}\"]]}]}" 211 | 212 | send_metrics_payload "$metrics_payload" "application/json" "$METRICS_ENDPOINT" 213 | } 214 | 215 | # Main dispatcher function 216 | send_metrics() { 217 | # Extract environment variables 218 | local phase="$CV4PVE_AUTOSNAP_PHASE" 219 | local vmid="$CV4PVE_AUTOSNAP_VMID" 220 | local vmname="$CV4PVE_AUTOSNAP_VMNAME" 221 | local vmtype="$CV4PVE_AUTOSNAP_VMTYPE" 222 | local label="$CV4PVE_AUTOSNAP_LABEL" 223 | local keep="$CV4PVE_AUTOSNAP_KEEP" 224 | local snap_name="$CV4PVE_AUTOSNAP_SNAP_NAME" 225 | local vmstate="$CV4PVE_AUTOSNAP_VMSTATE" 226 | local duration="$CV4PVE_AUTOSNAP_DURATION" 227 | local status="$CV4PVE_AUTOSNAP_STATE" 228 | local debug="$CV4PVE_AUTOSNAP_DEBUG" 229 | local dry_run="$CV4PVE_AUTOSNAP_DRY_RUN" 230 | local timestamp=$(date -u +%s) 231 | 232 | # Select the appropriate function based on METRICS_TYPE 233 | case "$METRICS_TYPE" in 234 | "prometheus") 235 | send_prometheus_metrics "$phase" "$vmid" "$vmname" "$vmtype" "$label" "$keep" "$snap_name" "$vmstate" "$duration" "$status" "$timestamp" 236 | ;; 237 | "influxdb") 238 | send_influxdb_metrics "$phase" "$vmid" "$vmname" "$vmtype" "$label" "$keep" "$snap_name" "$vmstate" "$duration" "$status" "$timestamp" 239 | ;; 240 | "json") 241 | send_json_metrics "$phase" "$vmid" "$vmname" "$vmtype" "$label" "$keep" "$snap_name" "$vmstate" "$duration" "$status" "$debug" "$dry_run" "$timestamp" 242 | ;; 243 | "grafana"|"loki") 244 | send_loki_metrics "$phase" "$vmid" "$vmname" "$vmtype" "$label" "$keep" "$snap_name" "$vmstate" "$duration" "$status" "$debug" "$dry_run" "$timestamp" 245 | ;; 246 | *) 247 | echo "Unknown METRICS_TYPE: $METRICS_TYPE. Supported types: prometheus, influxdb, json, grafana/loki" 248 | return 1 249 | ;; 250 | esac 251 | 252 | # Log what was sent based on phase 253 | case "$phase" in 254 | snap-create-post) 255 | echo "Sent $METRICS_TYPE metrics for VM $vmid (Label: $label, Duration: $duration)" 256 | ;; 257 | clean-job-end) 258 | echo "Sent $METRICS_TYPE cleanup metrics for VM $vmid (Label: $label)" 259 | ;; 260 | *) 261 | # For other phases, just log if needed 262 | echo "Phase $phase - metrics sent based on configuration" 263 | ;; 264 | esac 265 | } 266 | 267 | # Execute the function 268 | send_metrics -------------------------------------------------------------------------------- /src/Corsinvest.ProxmoxVE.AutoSnap.Api/Application.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: GPL-3.0-only 3 | * SPDX-FileCopyrightText: Copyright Corsinvest Srl 4 | */ 5 | 6 | using System.ComponentModel; 7 | using System.Diagnostics; 8 | using System.Text; 9 | using Corsinvest.ProxmoxVE.Api; 10 | using Corsinvest.ProxmoxVE.Api.Extension; 11 | using Corsinvest.ProxmoxVE.Api.Extension.Utils; 12 | using Corsinvest.ProxmoxVE.Api.Shared.Models.Cluster; 13 | using Corsinvest.ProxmoxVE.Api.Shared.Models.Vm; 14 | using Corsinvest.ProxmoxVE.Api.Shared.Utils; 15 | using Microsoft.Extensions.Logging; 16 | 17 | namespace Corsinvest.ProxmoxVE.AutoSnap.Api; 18 | 19 | /// 20 | /// Command autosnap. 21 | /// 22 | public class Application 23 | { 24 | private readonly ILogger _logger; 25 | 26 | /// 27 | /// Permissions request 28 | /// 29 | /// 30 | public IEnumerable Permissions { get; } = ["VM.Audit", "VM.Snapshot", "Datastore.Audit", "Pool.Allocate"]; 31 | 32 | private static readonly string Prefix = "auto"; 33 | 34 | /// 35 | /// Default time stamp format 36 | /// 37 | public static readonly string DefaultTimestampFormat = "yyMMddHHmmss"; 38 | 39 | /// 40 | /// Application name 41 | /// 42 | public static readonly string Name = "cv4pve-autosnap"; 43 | 44 | /// 45 | /// Old application name 46 | /// 47 | private static readonly string OldName = "eve4pve-autosnap"; 48 | 49 | private readonly PveClient _client; 50 | private readonly bool _dryRun; 51 | private readonly TextWriter _out; 52 | 53 | /// 54 | /// Constructor command 55 | /// 56 | /// 57 | /// 58 | /// 59 | /// 60 | public Application(PveClient client, ILoggerFactory loggerFactory, TextWriter @out, bool dryRun) 61 | { 62 | _client = client; 63 | _dryRun = dryRun; 64 | _out = @out; 65 | _logger = loggerFactory.CreateLogger(); 66 | } 67 | 68 | private static string GetTimestampFormat(string timestampFormat) 69 | => string.IsNullOrWhiteSpace(timestampFormat) 70 | ? DefaultTimestampFormat 71 | : timestampFormat; 72 | 73 | /// 74 | /// Get label from description 75 | /// 76 | /// 77 | /// 78 | /// 79 | public static string GetLabelFromName(string name, string timestampFormat) 80 | { 81 | var tmsLen = GetTimestampFormat(timestampFormat).Length; 82 | var prfLen = Prefix.Length; 83 | return tmsLen + prfLen < name.Length ? name[prfLen..^tmsLen] : string.Empty; 84 | } 85 | 86 | /// 87 | /// Event phase. 88 | /// 89 | public event EventHandler? PhaseEvent; 90 | 91 | /// 92 | /// Phases 93 | /// 94 | /// 95 | public static Dictionary Phases { get; } = new() 96 | { 97 | { "clean-job-start", HookPhase.CleanJobStart }, 98 | { "clean-job-end", HookPhase.CleanJobEnd }, 99 | { "snap-job-start", HookPhase.SnapJobStart }, 100 | { "snap-job-end", HookPhase.SnapJobEnd }, 101 | { "snap-create-pre", HookPhase.SnapCreatePre }, 102 | { "snap-create-post", HookPhase.SnapCreatePost }, 103 | { "snap-create-abort", HookPhase.SnapCreateAbort }, 104 | { "snap-remove-pre", HookPhase.SnapRemovePre }, 105 | { "snap-remove-post", HookPhase.SnapRemovePost }, 106 | { "snap-remove-abort", HookPhase.SnapRemoveAbort } 107 | }; 108 | 109 | /// 110 | /// Phase string to enum 111 | /// 112 | /// 113 | /// 114 | public static HookPhase PhaseStrToEnum(string phase) => Phases[phase]; 115 | 116 | /// 117 | /// Phase enum to string 118 | /// 119 | /// 120 | /// 121 | public static string PhaseEnumToStr(HookPhase phase) => Phases.SingleOrDefault(a => a.Value == phase).Key; 122 | 123 | private void CallPhaseEvent(HookPhase phase, 124 | IClusterResourceVm vm, 125 | string label, 126 | int keep, 127 | string snapName, 128 | bool vmState, 129 | double duration, 130 | bool status) 131 | { 132 | _logger.LogDebug("Phase: {phase}", PhaseEnumToStr(phase)); 133 | 134 | try 135 | { 136 | PhaseEvent?.Invoke(this, new PhaseEventArgs(phase, 137 | vm, 138 | label, 139 | keep, 140 | snapName, 141 | vmState, 142 | duration, 143 | status)); 144 | } 145 | catch (Exception ex) { _logger.LogError(ex, ex.Message); } 146 | } 147 | 148 | private async Task> GetVmsAsync(string vmIdsOrNames) 149 | => (await _client.GetVmsAsync(vmIdsOrNames)).Where(a => !a.IsUnknown); 150 | 151 | /// 152 | /// Status auto snapshot. 153 | /// 154 | /// 155 | /// 156 | /// 157 | public async Task>> StatusAsync(string vmIdsOrNames, string label, string timestampFormat) 158 | { 159 | timestampFormat = GetTimestampFormat(timestampFormat); 160 | 161 | var ret = new Dictionary>(); 162 | 163 | foreach (var vm in await GetVmsAsync(vmIdsOrNames)) 164 | { 165 | var snapshots = FilterApp(await SnapshotHelper.GetSnapshotsAsync(_client, vm.Node, vm.VmType, vm.VmId)); 166 | if (!string.IsNullOrWhiteSpace(label)) { snapshots = FilterLabel(snapshots, label, timestampFormat); } 167 | ret.Add(vm, snapshots); 168 | } 169 | return ret; 170 | } 171 | 172 | private static IEnumerable FilterApp(IEnumerable snapshots) 173 | => snapshots.Where(a => (a.Description + string.Empty).Replace("\n", string.Empty) == Name 174 | || (a.Description + string.Empty).Replace("\n", string.Empty) == OldName); 175 | 176 | private static string GetPrefix(string label) => Prefix + label; 177 | 178 | private static IEnumerable FilterLabel(IEnumerable snapshots, string label, string timestampFormat) 179 | { 180 | var lenTms = GetTimestampFormat(timestampFormat).Length; 181 | return FilterApp(snapshots.Where(a => (a.Name.Length - lenTms) > 0 && a.Name[..^lenTms] == GetPrefix(label))); 182 | } 183 | 184 | /// 185 | /// Execute a autosnap. 186 | /// 187 | /// 188 | /// 189 | /// 190 | /// 191 | /// 192 | /// 193 | /// 194 | /// 195 | /// 196 | public async Task SnapAsync(string vmIdsOrNames, 197 | string label, 198 | int keep, 199 | bool state, 200 | long timeout, 201 | string timestampFormat, 202 | int maxPercentageStorage, 203 | bool onlyRuns) 204 | { 205 | timestampFormat = GetTimestampFormat(timestampFormat); 206 | var pveFullVersion = (await _client.Version.Version()).ToData().version as string; 207 | var pveVersion = double.Parse(pveFullVersion!.Split(".")[0]); 208 | 209 | _out.WriteLine($@"ACTION Snap 210 | PVE Version: {pveFullVersion} 211 | VMs: {vmIdsOrNames} 212 | Label: {label} 213 | Keep: {keep} 214 | State: {state} 215 | Only running: {onlyRuns} 216 | Timeout: {Math.Round(timeout / 1000.0, 1)} sec. 217 | Timestamp format: {timestampFormat} 218 | Max % Storage : {maxPercentageStorage}%"); 219 | 220 | var snapName = GetPrefix(label) + DateTime.Now.ToString(timestampFormat); 221 | var ret = new ResultSnap 222 | { 223 | SnapName = snapName 224 | }; 225 | ret.Start(); 226 | 227 | CallPhaseEvent(HookPhase.SnapJobStart, null!, label, keep, null!, state, 0, true); 228 | 229 | var storagesCheck = new Dictionary(); 230 | var storagesPrint = new List(); 231 | 232 | var vms = await GetVmsAsync(vmIdsOrNames); 233 | if (!vms.Any()) 234 | { 235 | _out.WriteLine($"----- VMs with '{vmIdsOrNames}' NOT FOUND -----"); 236 | _out.WriteLine($"----- POSSIBLE PROBLEM PERMISSION 'VM.Audit' -----"); 237 | } 238 | 239 | var nodes = vms.Select(a => a.Node).Distinct().ToList(); 240 | 241 | var checkStorage = pveVersion >= 6; 242 | 243 | if (checkStorage) 244 | { 245 | var contentAllowed = new[] { "images", "rootdir" }; 246 | 247 | var storages = (await _client.GetStoragesAsync()) 248 | .Where(a => !a.IsUnknown && nodes.Contains(a.Node)) 249 | .ToList(); 250 | 251 | if (storages.Exists(a => string.IsNullOrWhiteSpace(a.Content))) 252 | { 253 | //content not exists 254 | //found in nodes/storages 255 | foreach (var node in nodes) 256 | { 257 | var nodeStorages = await _client.Nodes[node].Storage.GetAsync(string.Join(",", contentAllowed)); 258 | 259 | foreach (var storage in storages.Where(a => a.Node == node)) 260 | { 261 | storage.Content = nodeStorages.FirstOrDefault(a => a.Storage == storage.Storage 262 | && a.Type == storage.PluginType) 263 | ?.Content ?? string.Empty; 264 | } 265 | } 266 | } 267 | 268 | storages = storages.Where(a => a.Content.Split(',') 269 | .Any(a => contentAllowed.Contains(a))) 270 | .OrderBy(a => a.Node) 271 | .ThenBy(a => a.Storage) 272 | .ToList(); 273 | 274 | if (storages.Count == 0) { _out.WriteLine($"----- POSSIBLE PROBLEM PERMISSION 'Datastore.Audit' -----"); } 275 | 276 | //check storage capacity 277 | foreach (var storage in storages) 278 | { 279 | var valid = !(storage.DiskUsage == 0 280 | || storage.DiskSize == 0 281 | || storage.DiskUsagePercentage > maxPercentageStorage); 282 | 283 | var key = $"{storage.Node}/{storage.Storage}"; 284 | storagesPrint.Add(item: 285 | [ 286 | key, 287 | storage.PluginType, 288 | valid? "Ok": "Ko", 289 | Math.Round(storage.DiskUsagePercentage * 100, 1), 290 | FormatHelper.FromBytes(storage.DiskSize), 291 | FormatHelper.FromBytes(storage.DiskUsage), 292 | ]); 293 | 294 | storagesCheck.Add(key, valid); 295 | } 296 | 297 | if (storagesPrint.Count != 0) 298 | { 299 | var size = new[] { 25, 10, 10, 10, 12, 12 }; 300 | 301 | string FormatLine(object[] values) 302 | { 303 | var ret = new StringBuilder(); 304 | for (int i = 0; i < size.Length; i++) { ret.Append((values[i] + string.Empty).PadLeft(size[i])); } 305 | return ret.ToString(); 306 | } 307 | 308 | _out.WriteLine(FormatLine(["Storage", "Type", "Valid", "Used % ", "Disk Size", "Disk Usage"])); 309 | foreach (var item in storagesPrint) { _out.WriteLine(FormatLine(item)); } 310 | } 311 | } 312 | else 313 | { 314 | _out.WriteLine($"The Proxmox VE version {pveFullVersion} does not verify the storage % usage!"); 315 | } 316 | 317 | foreach (var vm in vms) 318 | { 319 | _out.WriteLine($"----- VM {vm.VmId} {vm.Type} {vm.Status} -----"); 320 | 321 | if (!vm.IsRunning && onlyRuns) 322 | { 323 | _out.WriteLine("Skip VM '--only-running' parameter used!"); 324 | continue; 325 | } 326 | 327 | //exclude template 328 | if (vm.IsTemplate) 329 | { 330 | _out.WriteLine("Skip VM is template"); 331 | continue; 332 | } 333 | 334 | VmConfig vmConfig = vm.VmType switch 335 | { 336 | VmType.Qemu => await _client.Nodes[vm.Node].Qemu[vm.VmId].Config.GetAsync(), 337 | VmType.Lxc => await _client.Nodes[vm.Node].Lxc[vm.VmId].Config.GetAsync(), 338 | _ => throw new InvalidEnumArgumentException(), 339 | }; 340 | 341 | var resultSnapVm = new ResultSnapVm 342 | { 343 | VmId = vm.VmId 344 | }; 345 | ret.Vms.Add(resultSnapVm); 346 | resultSnapVm.Start(); 347 | 348 | //check agent enabled 349 | if (vm.VmType == VmType.Qemu && !((VmConfigQemu)vmConfig).AgentEnabled) 350 | { 351 | _out.WriteLine($"VM {vm.VmId} consider enabling QEMU agent see https://pve.proxmox.com/wiki/Qemu-guest-agent"); 352 | } 353 | 354 | if (checkStorage && vmConfig.Disks.Any()) 355 | { 356 | //verify storage 357 | var validStorage = false; 358 | foreach (var item in vmConfig.Disks) 359 | { 360 | storagesCheck.TryGetValue($"{vm.Node}/{item.Storage}", out validStorage); 361 | if (!validStorage) { break; } 362 | } 363 | 364 | if (!validStorage) 365 | { 366 | _out.WriteLine($"Skip VM problem storage space out of {maxPercentageStorage}%"); 367 | resultSnapVm.Stop(); 368 | continue; 369 | } 370 | } 371 | 372 | //create snapshot 373 | CallPhaseEvent(HookPhase.SnapCreatePre, vm, label, keep, snapName, state, 0, true); 374 | 375 | _out.WriteLine($"Create snapshot: {snapName}"); 376 | 377 | var inError = true; 378 | if (!_dryRun) 379 | { 380 | try 381 | { 382 | var result = await SnapshotHelper.CreateSnapshotAsync(_client, 383 | vm.Node, 384 | vm.VmType, 385 | vm.VmId, 386 | snapName, 387 | Name, 388 | state, 389 | timeout); 390 | 391 | inError = await CheckResultAsync(result); 392 | } 393 | catch (Exception ex) 394 | { 395 | _logger.LogError(ex, ex.Message); 396 | _out.WriteLine(ex.Message); 397 | } 398 | } 399 | 400 | if (inError) 401 | { 402 | resultSnapVm.Stop(); 403 | CallPhaseEvent(HookPhase.SnapCreateAbort, vm, label, keep, snapName, state, resultSnapVm.Elapsed.TotalSeconds, false); 404 | continue; 405 | } 406 | 407 | //remove old snapshot 408 | if (!await SnapshotsRemoveAsync(vm, label, keep, timeout, timestampFormat)) 409 | { 410 | resultSnapVm.Stop(); 411 | continue; 412 | } 413 | 414 | resultSnapVm.Stop(); 415 | resultSnapVm.Status = true; 416 | 417 | CallPhaseEvent(HookPhase.SnapCreatePost, vm, label, keep, snapName, state, resultSnapVm.Elapsed.TotalSeconds, resultSnapVm.Status); 418 | 419 | _out.WriteLine($"VM execution {resultSnapVm.Elapsed}"); 420 | } 421 | 422 | ret.Stop(); 423 | 424 | CallPhaseEvent(HookPhase.SnapJobEnd, null!, label, keep, null!, state, ret.Elapsed.TotalSeconds, ret.Status); 425 | 426 | _out.WriteLine($"Total execution {ret.Elapsed}"); 427 | 428 | _logger.LogDebug($"Snap Exit: {ret.Status}"); 429 | 430 | return ret; 431 | } 432 | 433 | /// 434 | /// Clean autosnap. 435 | /// 436 | /// 437 | /// 438 | /// 439 | /// 440 | /// 441 | /// 442 | public async Task CleanAsync(string vmIdsOrNames, string label, int keep, long timeout, string timestampFormat) 443 | { 444 | timestampFormat = GetTimestampFormat(timestampFormat); 445 | 446 | _out.WriteLine($@"ACTION Clean 447 | VMs: {vmIdsOrNames} 448 | Label: {label} 449 | Keep: {keep} 450 | Timeout: {Math.Round(timeout / 1000.0, 1)} sec. 451 | Timestamp format: {timestampFormat}"); 452 | 453 | var watch = new Stopwatch(); 454 | watch.Start(); 455 | 456 | var ret = true; 457 | CallPhaseEvent(HookPhase.CleanJobStart, null!, label, keep, null!, false, 0, false); 458 | 459 | foreach (var vm in await GetVmsAsync(vmIdsOrNames)) 460 | { 461 | //exclude template 462 | if (vm.IsTemplate) 463 | { 464 | _out.WriteLine("Skip VM is template"); 465 | continue; 466 | } 467 | 468 | _out.WriteLine($"----- VM {vm.VmId} {vm.Type} -----"); 469 | if (!await SnapshotsRemoveAsync(vm, label, keep, timeout, timestampFormat)) { ret = false; } 470 | } 471 | 472 | watch.Stop(); 473 | CallPhaseEvent(HookPhase.CleanJobEnd, null!, label, keep, null!, false, watch.Elapsed.TotalSeconds, true); 474 | 475 | return ret; 476 | } 477 | 478 | private async Task CheckResultAsync(Result result) 479 | { 480 | var inError = result.InError(); 481 | if (inError) { _out.WriteLine(result.GetError()); } 482 | 483 | //check error in task 484 | if (await _client.TaskIsRunningAsync(result.ToData())) 485 | { 486 | _out.WriteLine($"Error task in run... increase the timeout!"); 487 | inError = true; 488 | } 489 | else 490 | { 491 | var taskStatus = await _client.GetExitStatusTaskAsync(result.ToData()); 492 | if (taskStatus != "OK") 493 | { 494 | _out.WriteLine($"Error in task: {taskStatus}"); 495 | inError = true; 496 | } 497 | } 498 | 499 | return inError; 500 | } 501 | 502 | private async Task SnapshotsRemoveAsync(IClusterResourceVm vm, 503 | string label, 504 | int keep, 505 | long timeout, 506 | string timestampFormat) 507 | { 508 | foreach (var snapshot in FilterLabel(await SnapshotHelper.GetSnapshotsAsync(_client, vm.Node, vm.VmType, vm.VmId), 509 | label, 510 | timestampFormat).Reverse().Skip(keep).Reverse()) 511 | { 512 | var watch = new Stopwatch(); 513 | watch.Start(); 514 | 515 | CallPhaseEvent(HookPhase.SnapRemovePre, vm, label, keep, snapshot.Name, false, 0, false); 516 | 517 | _out.WriteLine($"Remove snapshot: {snapshot.Name}"); 518 | 519 | var inError = false; 520 | if (!_dryRun) 521 | { 522 | try 523 | { 524 | var result = await SnapshotHelper.RemoveSnapshotAsync(_client, 525 | vm.Node, 526 | vm.VmType, 527 | vm.VmId, 528 | snapshot.Name, 529 | timeout, 530 | true); 531 | inError = await CheckResultAsync(result); 532 | } 533 | catch (Exception ex) 534 | { 535 | _logger.LogError(ex, ex.Message); 536 | _out.WriteLine(ex.Message); 537 | } 538 | } 539 | 540 | watch.Stop(); 541 | if (inError) 542 | { 543 | _logger.LogWarning($"Snap remove: problem in remove "); 544 | 545 | CallPhaseEvent(HookPhase.SnapRemoveAbort, 546 | vm, 547 | label, 548 | keep, 549 | snapshot.Name, 550 | false, 551 | watch.Elapsed.TotalSeconds, 552 | false); 553 | return false; 554 | } 555 | 556 | CallPhaseEvent(HookPhase.SnapRemovePost, 557 | vm, 558 | label, 559 | keep, 560 | snapshot.Name, 561 | false, 562 | watch.Elapsed.TotalSeconds, 563 | true); 564 | } 565 | 566 | return true; 567 | } 568 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cv4pve-autosnap 2 | 3 | ``` 4 | ______ _ __ 5 | / ____/___ __________(_)___ _ _____ _____/ /_ 6 | / / / __ \/ ___/ ___/ / __ \ | / / _ \/ ___/ __/ 7 | / /___/ /_/ / / (__ ) / / / / |/ / __(__ ) /_ 8 | \____/\____/_/ /____/_/_/ /_/|___/\___/____/\__/ 9 | 10 | Automatic Snapshot Tool for Proxmox VE (Made in Italy) 11 | ``` 12 | 13 | [![License](https://img.shields.io/github/license/Corsinvest/cv4pve-autosnap.svg?style=flat-square)](LICENSE.md) 14 | [![Release](https://img.shields.io/github/release/Corsinvest/cv4pve-autosnap.svg?style=flat-square)](https://github.com/Corsinvest/cv4pve-autosnap/releases/latest) 15 | [![Downloads](https://img.shields.io/github/downloads/Corsinvest/cv4pve-autosnap/total.svg?style=flat-square&logo=download)](https://github.com/Corsinvest/cv4pve-autosnap/releases) 16 | [![NuGet](https://img.shields.io/nuget/v/Corsinvest.ProxmoxVE.AutoSnap.Api.svg?style=flat-square&logo=nuget)](https://www.nuget.org/packages/Corsinvest.ProxmoxVE.AutoSnap.Api/) 17 | 18 | --- 19 | 20 | ## Quick Start 21 | 22 | ```bash 23 | # Check available releases at: https://github.com/Corsinvest/cv4pve-autosnap/releases 24 | # Download specific version (replace VERSION with actual version number) 25 | wget https://github.com/Corsinvest/cv4pve-autosnap/releases/download/VERSION/cv4pve-autosnap-linux-x64.zip 26 | unzip cv4pve-autosnap-linux-x64.zip 27 | 28 | # Create snapshot with retention 29 | ./cv4pve-autosnap --host=YOUR_HOST --username=root@pam --password=YOUR_PASSWORD --vmid=100 snap --label=daily --keep=7 30 | 31 | # Clean old snapshots 32 | ./cv4pve-autosnap --host=YOUR_HOST --username=root@pam --password=YOUR_PASSWORD --vmid=100 clean --label=daily 33 | ``` 34 | 35 | --- 36 | 37 | ## Features 38 | 39 | ### Core Capabilities 40 | 41 | #### **Performance & Reliability** 42 | - **Native C#** implementation 43 | - **Cross-platform** (Windows, Linux, macOS) 44 | - **API-based** operation (no root access required) 45 | - **Cluster support** with automatic VM/CT resolution 46 | - **High availability** with multiple host support 47 | 48 | #### **Flexible Targeting** 49 | - **ID or name** based VM/CT selection 50 | - **Bulk operations** with pattern matching 51 | - **Pool-based** selections (`@pool-customer1`) 52 | - **Tag-based** selections (`@tag-production`) 53 | - **Node-specific** operations (`@node-pve1`) 54 | 55 | #### **Advanced Management** 56 | - **Intelligent retention** with configurable keep policies 57 | - **Multiple schedules** using labels (daily, weekly, monthly) 58 | - **Hook scripts** for custom automation 59 | - **Memory state** preservation with `--state` 60 | - **Storage monitoring** with capacity checks 61 | 62 | #### **Enterprise Features** 63 | - **API token** support (Proxmox VE 6.2+) 64 | - **SSL validation** options 65 | - **Timeout management** for operations 66 | - **Error resilience** (no stop on error) 67 | - **Comprehensive logging** and status reporting 68 | 69 | --- 70 | 71 | ## Installation 72 | 73 | ### Linux Installation 74 | 75 | ```bash 76 | # Check available releases and get the specific version number 77 | # Visit: https://github.com/Corsinvest/cv4pve-autosnap/releases 78 | 79 | # Download specific version (replace VERSION with actual version like v1.2.3) 80 | wget https://github.com/Corsinvest/cv4pve-autosnap/releases/download/VERSION/cv4pve-autosnap-linux-x64.zip 81 | 82 | # Alternative: Get latest release URL programmatically 83 | LATEST_URL=$(curl -s https://api.github.com/repos/Corsinvest/cv4pve-autosnap/releases/latest | grep browser_download_url | grep linux-x64 | cut -d '"' -f 4) 84 | wget "$LATEST_URL" 85 | 86 | # Extract and make executable 87 | unzip cv4pve-autosnap-linux-x64.zip 88 | chmod +x cv4pve-autosnap 89 | 90 | # Optional: Move to system path 91 | sudo mv cv4pve-autosnap /usr/local/bin/ 92 | ``` 93 | 94 | ### Windows Installation 95 | 96 | ```powershell 97 | # Check available releases at: https://github.com/Corsinvest/cv4pve-autosnap/releases 98 | # Download specific version (replace VERSION with actual version) 99 | Invoke-WebRequest -Uri "https://github.com/Corsinvest/cv4pve-autosnap/releases/download/VERSION/cv4pve-autosnap-win-x64.zip" -OutFile "cv4pve-autosnap.zip" 100 | 101 | # Extract 102 | Expand-Archive cv4pve-autosnap.zip -DestinationPath "C:\Tools\cv4pve-autosnap" 103 | 104 | # Add to PATH (optional) 105 | $env:PATH += ";C:\Tools\cv4pve-autosnap" 106 | ``` 107 | 108 | ### macOS Installation 109 | 110 | ```bash 111 | # Check available releases at: https://github.com/Corsinvest/cv4pve-autosnap/releases 112 | # Download specific version (replace VERSION with actual version) 113 | wget https://github.com/Corsinvest/cv4pve-autosnap/releases/download/VERSION/cv4pve-autosnap-osx-x64.zip 114 | unzip cv4pve-autosnap-osx-x64.zip 115 | chmod +x cv4pve-autosnap 116 | 117 | # Move to applications 118 | sudo mv cv4pve-autosnap /usr/local/bin/ 119 | ``` 120 | 121 | --- 122 | 123 | ## Configuration 124 | 125 | ### Authentication Methods 126 | 127 | #### **Username/Password** 128 | ```bash 129 | cv4pve-autosnap --host=192.168.1.100 --username=backup@pve --password=your_password [commands] 130 | ``` 131 | 132 | #### **API Token (Recommended)** 133 | ```bash 134 | cv4pve-autosnap --host=192.168.1.100 --api-token=backup@pve!token1=uuid-here [commands] 135 | ``` 136 | 137 | #### **Password from File** 138 | ```bash 139 | # Use interactive password prompt that stores to file 140 | cv4pve-autosnap --host=192.168.1.100 --username=backup@pve --password=file:/etc/cv4pve/password [commands] 141 | 142 | # First run: prompts for password and saves to file 143 | # Subsequent runs: reads password from file automatically 144 | ``` 145 | 146 | ### VM/CT Selection Patterns 147 | 148 | The `--vmid` parameter supports powerful pattern matching based on the `GetVmsAsync` method with jolly patterns: 149 | 150 | | Pattern | Syntax | Description | Example | 151 | |---------|--------|-------------|---------| 152 | | **Single ID** | `ID` | Specific VM/CT by ID | `--vmid=100` | 153 | | **Single Name** | `name` | Specific VM/CT by name | `--vmid=web-server` | 154 | | **Multiple IDs** | `ID,ID,ID` | Comma-separated list | `--vmid=100,101,102` | 155 | | **Mixed ID/Names** | `ID,name,ID` | Mix IDs and names | `--vmid=100,web-server,102` | 156 | | **ID Ranges** | `start:end` | Range of IDs (inclusive) | `--vmid=100:110` | 157 | | **Complex Ranges** | `start:end,ID,start:end` | Multiple ranges and IDs | `--vmid=100:107,200,300:305` | 158 | | **Wildcard Names** | `%pattern%` | Contains pattern | `--vmid=%web%,%db%` | 159 | | **Prefix Match** | `pattern%` | Starts with pattern | `--vmid=web%,db%` | 160 | | **Suffix Match** | `%pattern` | Ends with pattern | `--vmid=%server,%prod` | 161 | | **All VMs** | `@all` | All VMs in cluster | `--vmid=@all` | 162 | | **All on Node** | `@all-node` | All VMs on specific node | `--vmid=@all-pve1` | 163 | | **All on Current** | `@all-$(hostname)` | All VMs on current host | `--vmid=@all-$(hostname)` | 164 | | **Pool Selection** | `@pool-name` | All VMs in specific pool | `--vmid=@pool-production` | 165 | | **Tag Selection** | `@tag-name` | All VMs with specific tag | `--vmid=@tag-backup` | 166 | | **Node Selection** | `@node-name` | All VMs on specific node | `--vmid=@node-pve1` | 167 | | **Node Current** | `@node-$(hostname)` | All VMs on current host | `--vmid=@node-$(hostname)` | 168 | | **Exclusions** | `-ID` or `-name` | Exclude specific VM/CT | `--vmid=@all,-100,-test-vm` | 169 | | **Wildcard Exclusions** | `-%pattern%` | Exclude pattern matches | `--vmid=@all,-%test%,-%dev%` | 170 | | **Range Exclusions** | `-start:end` | Exclude range of IDs | `--vmid=@all,-200:299` | 171 | | **Tag Exclusions** | `-@tag-name` | Exclude VMs with tag | `--vmid=@all,-@tag-test` | 172 | | **Pool Exclusions** | `-@pool-name` | Exclude pool VMs | `--vmid=@pool-prod,-@pool-dev` | 173 | | **Node Exclusions** | `-@node-name` | Exclude node VMs | `--vmid=@all,-@node-pve2` | 174 | 175 | ### Pattern Examples 176 | 177 |
178 | Real-World Selection Examples 179 | 180 | #### Basic Selections 181 | ```bash 182 | # Single VM by ID 183 | --vmid=100 184 | 185 | # Single VM by name 186 | --vmid=database-server 187 | 188 | # Multiple VMs mixed 189 | --vmid=100,web-server,102,mail-server 190 | 191 | # Range of VMs 192 | --vmid=100:110 193 | 194 | # Complex ranges 195 | --vmid=100:107,-105,200:204,300 196 | 197 | # Wildcard patterns with % 198 | --vmid=web%,db%,%server # web*, db*, *server 199 | --vmid=%test%,%dev% # *test*, *dev* 200 | ``` 201 | 202 | #### Advanced Pool & Tag Selections 203 | ```bash 204 | # All VMs in production pool 205 | --vmid=@pool-production 206 | 207 | # All VMs tagged as critical 208 | --vmid=@tag-critical 209 | 210 | # All VMs on specific node 211 | --vmid=@node-pve1 212 | 213 | # All VMs on current hostname 214 | --vmid=@node-$(hostname) 215 | ``` 216 | 217 | #### Exclusion Patterns 218 | ```bash 219 | # All VMs except specific IDs 220 | --vmid=@all,-100,-101 221 | 222 | # All VMs except by name 223 | --vmid=@all,-test-vm,-backup-server 224 | 225 | # All VMs except wildcard patterns 226 | --vmid=@all,-%test%,-%dev% 227 | 228 | # All VMs except range 229 | --vmid=@all,-200:299 230 | 231 | # Production pool except test tagged 232 | --vmid=@pool-production,-@tag-test 233 | 234 | # All except specific node 235 | --vmid=@all,-@node-pve2 236 | 237 | # Complex exclusions with wildcards 238 | --vmid=@all,-100:110,-@tag-development,-%test% 239 | ``` 240 | 241 | #### Wildcard Pattern Examples 242 | ```bash 243 | # Contains pattern (equivalent to *web* in shell) 244 | --vmid=%web% # Matches: web-server, my-web-app, web01 245 | 246 | # Starts with pattern (equivalent to web* in shell) 247 | --vmid=web% # Matches: web-server, webmail, web01 248 | 249 | # Ends with pattern (equivalent to *server in shell) 250 | --vmid=%server # Matches: web-server, mail-server, db-server 251 | 252 | # Multiple wildcard patterns 253 | --vmid=web%,db%,%prod # web*, db*, *prod 254 | 255 | # Complex selections with wildcards 256 | --vmid=%web%,%db%,100:110,mail-server 257 | 258 | # Exclude wildcard patterns 259 | --vmid=@all,-%test%,-%dev%,-%staging% 260 | ``` 261 | 262 |
263 | 264 | --- 265 | 266 | ## Usage Examples 267 | 268 | ### Basic Snapshot Operations 269 | 270 |
271 | Create Snapshots 272 | 273 | #### Single VM Snapshot 274 | ```bash 275 | cv4pve-autosnap --host=pve.domain.com --username=root@pam --password=secret --vmid=100 snap --label=daily --keep=7 276 | ``` 277 | 278 | #### Multiple VMs with State 279 | ```bash 280 | cv4pve-autosnap --host=pve.domain.com --api-token=backup@pve!token=uuid --vmid=100,101,102 snap --label=backup --keep=3 --state 281 | ``` 282 | 283 | #### All VMs except exclusions 284 | ```bash 285 | cv4pve-autosnap --host=pve.domain.com --username=backup@pve --password=secret --vmid="@all,-100,-test-vm" snap --label=nightly --keep=5 286 | ``` 287 | 288 |
289 | 290 |
291 | Clean Old Snapshots 292 | 293 | #### Clean specific label 294 | ```bash 295 | cv4pve-autosnap --host=pve.domain.com --username=root@pam --password=secret --vmid=100 clean --label=daily 296 | ``` 297 | 298 | #### Clean with retention 299 | ```bash 300 | cv4pve-autosnap --host=pve.domain.com --api-token=backup@pve!token=uuid --vmid=@pool-production clean --label=weekly --keep=4 301 | ``` 302 | 303 |
304 | 305 |
306 | Status & Monitoring 307 | 308 | #### View snapshot status 309 | ```bash 310 | cv4pve-autosnap --host=pve.domain.com --username=root@pam --password=secret --vmid=100 status 311 | ``` 312 | 313 | #### JSON output for automation 314 | ```bash 315 | cv4pve-autosnap --host=pve.domain.com --api-token=backup@pve!token=uuid --vmid=@all status --output=json 316 | ``` 317 | 318 |
319 | 320 | ### Advanced Targeting Examples 321 | 322 |
323 | Production Environment Patterns 324 | 325 | ```bash 326 | # Backup all production VMs tagged as 'critical' 327 | cv4pve-autosnap --host=cluster.company.com --api-token=backup@pve!prod=uuid --vmid=@tag-critical snap --label=critical-daily --keep=14 328 | 329 | # Backup entire customer pool except test VMs 330 | cv4pve-autosnap --host=pve.company.com --username=backup@pve --password=secret --vmid="@pool-customer1,-@tag-test" snap --label=customer-backup --keep=30 331 | 332 | # Node-specific backup with range exclusion 333 | cv4pve-autosnap --host=pve-cluster.local --api-token=ops@pve!node1=uuid --vmid="@node-pve1,-200:299" snap --label=node1-backup --keep=7 334 | ``` 335 | 336 |
337 | 338 | ### Scheduling with Cron 339 | 340 |
341 | Cron Examples 342 | 343 | ```bash 344 | # Edit crontab 345 | crontab -e 346 | 347 | # Daily backup at 2 AM (keep 7 days) 348 | 0 2 * * * /usr/local/bin/cv4pve-autosnap --host=pve.local --api-token=backup@pve!daily=uuid --vmid=@tag-production snap --label=daily --keep=7 349 | 350 | # Weekly backup on Sunday at 3 AM (keep 4 weeks) 351 | 0 3 * * 0 /usr/local/bin/cv4pve-autosnap --host=pve.local --api-token=backup@pve!weekly=uuid --vmid=@all snap --label=weekly --keep=4 --state 352 | 353 | # Monthly cleanup on 1st day at 4 AM 354 | 0 4 1 * * /usr/local/bin/cv4pve-autosnap --host=pve.local --api-token=backup@pve!cleanup=uuid --vmid=@all clean --label=old-snapshots 355 | ``` 356 | 357 |
358 | 359 | --- 360 | 361 | ## Security & Permissions 362 | 363 | ### Required Permissions 364 | 365 | | Permission | Purpose | Scope | 366 | |------------|---------|-------| 367 | | **VM.Audit** | Read VM/CT information | Virtual machines | 368 | | **VM.Snapshot** | Create/delete snapshots | Virtual machines | 369 | | **Datastore.Audit** | Check storage capacity | Storage systems | 370 | | **Pool.Audit** | Access pool information | Resource pools | 371 | 372 | ### API Token Setup (Recommended) 373 | 374 |
375 | Creating API Tokens 376 | 377 | #### 1. Generate API Token and Configure Permissions 378 | ```bash 379 | # Follow Proxmox VE documentation for: 380 | # - API token creation with proper privilege separation 381 | # - Permission assignment for required roles 382 | # - Required permissions: VM.Audit, VM.Snapshot, Datastore.Audit, Pool.Audit 383 | # Refer to official Proxmox VE API documentation for detailed steps 384 | ``` 385 | 386 | #### 2. Use Token in Commands 387 | ```bash 388 | cv4pve-autosnap --host=pve.local --api-token=backup@pve!backup-token=uuid-from-creation [commands] 389 | ``` 390 | 391 |
392 | 393 | --- 394 | 395 | ## Advanced Features 396 | 397 | ### Hook Scripts 398 | 399 |
400 | Pre/Post Snapshot Automation 401 | 402 | Hook scripts receive environment variables for custom automation: 403 | 404 | ```bash 405 | CV4PVE_AUTOSNAP_PHASE # pre-snapshot, post-snapshot, pre-clean, post-clean 406 | CV4PVE_AUTOSNAP_VMID # VM/CT ID 407 | CV4PVE_AUTOSNAP_VMNAME # VM/CT name 408 | CV4PVE_AUTOSNAP_VMTYPE # qemu or lxc 409 | CV4PVE_AUTOSNAP_LABEL # Snapshot label 410 | CV4PVE_AUTOSNAP_KEEP # Retention count 411 | CV4PVE_AUTOSNAP_SNAP_NAME # Snapshot name 412 | CV4PVE_AUTOSNAP_VMSTATE # Memory state included (1/0) 413 | CV4PVE_AUTOSNAP_DURATION # Operation duration 414 | CV4PVE_AUTOSNAP_STATE # Operation status (1/0) 415 | CV4PVE_AUTOSNAP_DEBUG # Debug mode enabled (1/0) 416 | CV4PVE_AUTOSNAP_DRY_RUN # Dry run mode enabled (1/0) 417 | ``` 418 | 419 | The repository includes example hook scripts in the [hooks/](../hooks/) directory for different platforms: 420 | - **Windows Batch**: [hook-template.bat](../hooks/hook-template.bat) 421 | - **Unix/Linux**: [hook-template.sh](../hooks/hook-template.sh) 422 | - **PowerShell**: [hook-template.ps1](../hooks/hook-template.ps1) 423 | - **Metrics**: [send-metrics.sh](../hooks/send-metrics.sh) (for sending metrics to monitoring systems) 424 | 425 | These scripts provide templates with all available environment variables and phase handling logic that you can customize for your automation needs. 426 | 427 | #### Example Hook Script 428 | ```bash 429 | #!/bin/bash 430 | # /usr/local/bin/cv4pve-hook.sh 431 | 432 | case $CV4PVE_AUTOSNAP_PHASE in 433 | "pre-snapshot") 434 | echo "$(date): Starting snapshot for VM $CV4PVE_AUTOSNAP_VMID" 435 | # Send notification to monitoring system 436 | curl -X POST "https://monitoring.company.com/api/events" -d "{'type':'snapshot_start','vm':'$CV4PVE_AUTOSNAP_VMID'}" 437 | ;; 438 | "post-snapshot") 439 | echo "$(date): Completed snapshot $CV4PVE_AUTOSNAP_SNAP_NAME" 440 | # Log to central system 441 | logger "cv4pve-autosnap: Created snapshot $CV4PVE_AUTOSNAP_SNAP_NAME for VM $CV4PVE_AUTOSNAP_VMID" 442 | ;; 443 | esac 444 | ``` 445 | 446 | #### Using Hook Scripts 447 | ```bash 448 | cv4pve-autosnap --host=pve.local --api-token=backup@pve!token=uuid --vmid=100 --script-hook=/usr/local/bin/cv4pve-hook.sh snap --label=daily --keep=7 449 | ``` 450 | 451 |
452 | 453 | ### Storage Monitoring 454 | 455 |
456 | Intelligent Storage Management 457 | 458 | cv4pve-autosnap automatically monitors storage usage: 459 | 460 | ```bash 461 | # Set custom storage threshold (default: 95%) 462 | cv4pve-autosnap --host=pve.local --api-token=backup@pve!token=uuid --max-perc-storage=85 --vmid=@all snap --label=daily --keep=7 463 | ``` 464 | 465 | #### Storage Check Output 466 | ``` 467 | ---------------------------------------------------------------------------- 468 | | Storage | Type | Valid | Used % | Max Disk (GB) | Disk (GB) | 469 | ---------------------------------------------------------------------------- 470 | | pve-local/ssd | zfspool | Ok | 78.2 | 1000 | 782 | 471 | | pve-local/hdd | zfspool | Ok | 45.1 | 2000 | 902 | 472 | | nfs-backup | nfs | Ok | 23.4 | 5000 | 1170 | 473 | ---------------------------------------------------------------------------- 474 | ``` 475 | 476 |
477 | 478 | ### Custom Timestamp Formats 479 | 480 |
481 | Flexible Naming Patterns 482 | 483 | Customize snapshot naming with C# datetime format strings. The timestamp format follows the **[Microsoft .NET Custom Date and Time Format Strings](https://docs.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings)** specification. 484 | 485 | ```bash 486 | # Default format: yyMMddHHmmss (e.g., 250811143022) 487 | cv4pve-autosnap --vmid=100 snap --label=daily 488 | 489 | # ISO format: yyyy-MM-dd_HH-mm-ss (e.g., 2025-08-11_14-30-22) 490 | cv4pve-autosnap --vmid=100 --timestamp-format="yyyy-MM-dd_HH-mm-ss" snap --label=daily 491 | 492 | # Custom format with day names: yyyyMMdd_dddd_HHmmss (e.g., 20250811_Monday_143022) 493 | cv4pve-autosnap --vmid=100 --timestamp-format="yyyyMMdd_dddd_HHmmss" snap --label=backup 494 | 495 | # European format: dd.MM.yyyy-HH.mm.ss (e.g., 11.08.2025-14.30.22) 496 | cv4pve-autosnap --vmid=100 --timestamp-format="dd.MM.yyyy-HH.mm.ss" snap --label=daily 497 | ``` 498 | 499 | #### C# DateTime Format Specifiers 500 | 501 | | Specifier | Description | Example | Result | 502 | |-----------|-------------|---------|--------| 503 | | **Year** | 504 | | `yy` | 2-digit year | `yy` | 25 | 505 | | `yyyy` | 4-digit year | `yyyy` | 2025 | 506 | | **Month** | 507 | | `M` | Month (1-12) | `M` | 8 | 508 | | `MM` | Month (01-12) | `MM` | 08 | 509 | | `MMM` | Short month name | `MMM` | Aug | 510 | | `MMMM` | Full month name | `MMMM` | August | 511 | | **Day** | 512 | | `d` | Day (1-31) | `d` | 11 | 513 | | `dd` | Day (01-31) | `dd` | 11 | 514 | | `ddd` | Short day name | `ddd` | Mon | 515 | | `dddd` | Full day name | `dddd` | Monday | 516 | | **Hour** | 517 | | `h` | Hour 12-format (1-12) | `h` | 2 | 518 | | `hh` | Hour 12-format (01-12) | `hh` | 02 | 519 | | `H` | Hour 24-format (0-23) | `H` | 14 | 520 | | `HH` | Hour 24-format (00-23) | `HH` | 14 | 521 | | **Minute** | 522 | | `m` | Minute (0-59) | `m` | 30 | 523 | | `mm` | Minute (00-59) | `mm` | 30 | 524 | | **Second** | 525 | | `s` | Second (0-59) | `s` | 22 | 526 | | `ss` | Second (00-59) | `ss` | 22 | 527 | | **Millisecond** | 528 | | `f` | Tenths of second | `f` | 7 | 529 | | `ff` | Hundredths of second | `ff` | 72 | 530 | | `fff` | Milliseconds | `fff` | 722 | 531 | | **Time Period** | 532 | | `tt` | AM/PM designator | `tt` | PM | 533 | 534 | #### Common Format Examples 535 | 536 | | Format String | Example Output | Use Case | 537 | |---------------|----------------|----------| 538 | | `yyMMddHHmmss` | 250811143022 | Default compact format | 539 | | `yyyy-MM-dd_HH-mm-ss` | 2025-08-11_14-30-22 | ISO-like readable format | 540 | | `yyyyMMdd-HHmmss` | 20250811-143022 | Date-time separation | 541 | | `dd.MM.yyyy-HH.mm.ss` | 11.08.2025-14.30.22 | European format | 542 | | `yyyy_MM_dd_HH_mm_ss` | 2025_08_11_14_30_22 | Underscore format | 543 | | `yyyyMMdd_dddd` | 20250811_Monday | Date with day name | 544 | | `MMM_dd_yyyy_HHmm` | Aug_11_2025_1430 | Month abbreviation | 545 | 546 |
547 | 548 | ### Configuration Files 549 | 550 |
551 | Parameter Files for Complex Setups 552 | 553 | Use parameter files for complex configurations: 554 | 555 | #### Create Parameter File 556 | ```bash 557 | # /etc/cv4pve/production.conf 558 | --host=pve-cluster.company.com 559 | --api-token=backup@pve!production=uuid-here 560 | --vmid=@pool-production,-@tag-test 561 | --max-perc-storage=90 562 | --timeout=300 563 | --validate-certificate 564 | ``` 565 | 566 | #### Execute with Parameter File 567 | ```bash 568 | cv4pve-autosnap @/etc/cv4pve/production.conf snap --label=daily --keep=14 --state 569 | ``` 570 | 571 | #### Multiple Environment Configs 572 | ```bash 573 | # Development environment 574 | cv4pve-autosnap @/etc/cv4pve/dev.conf snap --label=dev-snapshot --keep=3 575 | 576 | # Staging environment 577 | cv4pve-autosnap @/etc/cv4pve/staging.conf snap --label=staging --keep=5 578 | 579 | # Production environment 580 | cv4pve-autosnap @/etc/cv4pve/production.conf snap --label=production --keep=30 581 | ``` 582 | 583 |
584 | 585 | --- 586 | 587 | ## Best Practices 588 | 589 | ### Snapshot Strategy Guidelines 590 | 591 | #### **Retention Schedules** 592 | - **Hourly**: Keep 24 snapshots (1 day) 593 | - **Daily**: Keep 7-14 snapshots (1-2 weeks) 594 | - **Weekly**: Keep 4-8 snapshots (1-2 months) 595 | - **Monthly**: Keep 12 snapshots (1 year) 596 | - **Yearly**: Keep 3-5 snapshots (long-term) 597 | 598 | #### **Labeling Convention** 599 | - Use descriptive labels (`daily`, `weekly`, `before-update`) 600 | - Include environment in label (`prod-daily`, `dev-weekly`) 601 | - Consider compliance requirements (`retention-7y`) 602 | 603 | #### **Performance Optimization** 604 | - **Schedule during low activity** periods 605 | - **Stagger snapshot times** across VMs 606 | - **Monitor storage growth** regularly 607 | - **Use memory state** sparingly (larger snapshots) 608 | - **Implement cleanup routines** for old snapshots 609 | 610 | #### **Reliability Measures** 611 | - **Test restore procedures** regularly 612 | - **Monitor snapshot creation** for failures 613 | - **Implement alerting** for storage thresholds 614 | - **Document recovery procedures** 615 | - **Verify QEMU agent** installation 616 | 617 | --- 618 | 619 | ## Troubleshooting 620 | 621 | ### Common Issues & Solutions 622 | 623 |
624 | Authentication Problems 625 | 626 | #### Issue: "Authentication failed" 627 | ```bash 628 | # Check credentials 629 | cv4pve-autosnap --host=pve.local --username=root@pam --password=test --vmid=100 status 630 | 631 | # Verify API token format 632 | cv4pve-autosnap --host=pve.local --api-token=user@realm!tokenid=uuid --vmid=100 status 633 | ``` 634 | 635 | #### Solution: Verify permissions 636 | ```bash 637 | # Check user permissions in Proxmox 638 | pveum user list 639 | pveum user permissions backup@pve 640 | ``` 641 | 642 |
643 | 644 |
645 | Connection Issues 646 | 647 | #### Issue: "Connection timeout" or "Host unreachable" 648 | ```bash 649 | # Test connectivity 650 | ping pve.local 651 | telnet pve.local 8006 652 | 653 | # Check SSL certificate 654 | cv4pve-autosnap --host=pve.local --validate-certificate --username=root@pam --password=secret --vmid=100 status 655 | ``` 656 | 657 | #### Solution: Network and certificate verification 658 | ```bash 659 | # Use IP instead of hostname 660 | cv4pve-autosnap --host=192.168.1.100 ... 661 | 662 | # Disable SSL validation for testing (not recommended for production) 663 | cv4pve-autosnap --host=pve.local --username=root@pam --password=secret --vmid=100 status 664 | ``` 665 | 666 |
667 | 668 |
669 | Storage Issues 670 | 671 | #### Issue: "Storage space insufficient" 672 | ```bash 673 | # Check storage status 674 | cv4pve-autosnap --host=pve.local --api-token=token --vmid=100 status 675 | ``` 676 | 677 | #### Solution: Adjust storage threshold 678 | ```bash 679 | # Lower storage threshold 680 | cv4pve-autosnap --host=pve.local --api-token=token --max-perc-storage=80 --vmid=100 snap --label=daily --keep=3 681 | ``` 682 | 683 |
684 | 685 |
686 | VM Selection Issues 687 | 688 | #### Issue: "No VMs found matching criteria" 689 | ```bash 690 | # Debug VM selection 691 | cv4pve-autosnap --host=pve.local --api-token=token --vmid=@tag-nonexistent status 692 | ``` 693 | 694 | #### Solution: Verify VM tags and pools 695 | ```bash 696 | # List all VMs to verify IDs/names 697 | cv4pve-autosnap --host=pve.local --api-token=token --vmid=@all status 698 | 699 | # Check specific pool 700 | cv4pve-autosnap --host=pve.local --api-token=token --vmid=@pool-production status 701 | ``` 702 | 703 |
704 | 705 | ### Debug Mode 706 | 707 |
708 | Enable Detailed Logging 709 | 710 | ```bash 711 | # Enable debug output 712 | cv4pve-autosnap --host=pve.local --api-token=token --vmid=100 --debug snap --label=debug-test --keep=1 713 | 714 | # Test run without making changes 715 | cv4pve-autosnap --host=pve.local --api-token=token --vmid=100 --dry-run snap --label=test --keep=3 716 | ``` 717 | 718 |
719 | 720 | --- 721 | 722 | ## Resources 723 | 724 | #### **Official Tutorial** 725 | 726 | [![cv4pve-autosnap Tutorial](http://img.youtube.com/vi/kM5KhD9seT4/maxresdefault.jpg)](https://www.youtube.com/watch?v=kM5KhD9seT4) 727 | 728 | **Complete setup and usage guide** 729 | 730 | #### **Web GUI Version** 731 | 732 | [![cv4pve-admin](https://raw.githubusercontent.com/Corsinvest/cv4pve-admin/main/src/Corsinvest.ProxmoxVE.Admin/wwwroot/doc/images/screenshot/modules/autosnap/modules-safe-autosnap.png)](https://github.com/Corsinvest/cv4pve-admin) 733 | 734 | **Web interface for cv4pve-autosnap** 735 | 736 | ### Documentation Links 737 | 738 | | Resource | Description | 739 | |----------|-------------| 740 | | **[QEMU Guest Agent](https://pve.proxmox.com/wiki/Qemu-guest-agent)** | Setup guide for QEMU guest agent | 741 | | **[API Documentation](https://pve.proxmox.com/pve-docs/api-viewer/index.html)** | Proxmox VE API reference | 742 | | **[cv4pve-tools Suite](https://www.cv4pve-tools.com)** | Complete cv4pve tools ecosystem | 743 | 744 | --- 745 | 746 | ## QEMU Guest Agent Integration 747 | 748 | ### Why QEMU Guest Agent Matters 749 | 750 | > **Important:** Without QEMU Guest Agent, snapshots are like "pulling the power plug" on your VM. The agent ensures filesystem consistency during snapshot operations. 751 | 752 | #### **Without Guest Agent** 753 | - Inconsistent filesystem state 754 | - Potential data corruption 755 | - Half-written files 756 | - Requires fsck on boot 757 | - Application data loss risk 758 | 759 | #### **With Guest Agent** 760 | - Filesystem sync before snapshot 761 | - I/O freeze during operation 762 | - Clean application state 763 | - Reliable snapshot consistency 764 | - Database-safe operations 765 | 766 | ### Guest Agent Verification 767 | 768 | ```bash 769 | # Check if guest agent is working 770 | cv4pve-autosnap --host=pve.local --api-token=token --vmid=100 status 771 | 772 | # Look for agent warnings in output: 773 | # "VM 100 consider enabling QEMU agent see https://pve.proxmox.com/wiki/Qemu-guest-agent" 774 | ``` 775 | 776 | --- 777 | 778 | ## Command Reference 779 | 780 | ### Global Options 781 | 782 |
783 | Complete Parameter List 784 | 785 | ```bash 786 | cv4pve-autosnap [global-options] [command] [command-options] 787 | ``` 788 | 789 | #### Authentication Options 790 | | Parameter | Description | Example | 791 | |-----------|-------------|---------| 792 | | `--host` | Proxmox host(s) | `--host=pve.local:8006` | 793 | | `--username` | Username@realm | `--username=backup@pve` | 794 | | `--password` | Password or file | `--password=secret` or `--password=file:/path` | 795 | | `--api-token` | API token | `--api-token=user@realm!token=uuid` | 796 | 797 | #### Connection Options 798 | | Parameter | Description | Default | 799 | |-----------|-------------|---------| 800 | | `--timeout` | Operation timeout (seconds) | `30` | 801 | | `--validate-certificate` | Validate SSL certificate | `false` | 802 | 803 | #### Target Selection 804 | | Parameter | Description | Example | 805 | |-----------|-------------|---------| 806 | | `--vmid` | VM/CT selection pattern | `--vmid=100,101,@pool-prod` | 807 | 808 | #### Storage & Format 809 | | Parameter | Description | Default | 810 | |-----------|-------------|---------| 811 | | `--max-perc-storage` | Max storage usage % | `95` | 812 | | `--timestamp-format` | Snapshot timestamp format | `yyMMddHHmmss` | 813 | 814 | #### Output Options 815 | | Parameter | Description | Options | 816 | |-----------|-------------|---------| 817 | | `--output` | Output format | `text`, `json`, `jsonpretty` | 818 | | `--debug` | Enable debug mode | `false` | 819 | | `--dry-run` | Test run without changes | `false` | 820 | 821 |
822 | 823 | ### Snap Command 824 | 825 |
826 | Snapshot Creation Options 827 | 828 | ```bash 829 | cv4pve-autosnap [global-options] snap [snap-options] 830 | ``` 831 | 832 | #### Snap-Specific Options 833 | | Parameter | Description | Required | 834 | |-----------|-------------|----------| 835 | | `--label` | Snapshot label/category | Yes | 836 | | `--keep` | Number of snapshots to retain | Yes | 837 | | `--state` | Include VM memory state | No | 838 | | `--script-hook` | Path to hook script | No | 839 | 840 | #### Examples 841 | ```bash 842 | # Basic snapshot with retention 843 | cv4pve-autosnap --host=pve.local --api-token=token --vmid=100 snap --label=daily --keep=7 844 | 845 | # Snapshot with memory state 846 | cv4pve-autosnap --host=pve.local --api-token=token --vmid=100 snap --label=backup --keep=3 --state 847 | 848 | # Snapshot with custom hook 849 | cv4pve-autosnap --host=pve.local --api-token=token --vmid=100 snap --label=daily --keep=7 --script-hook=/opt/scripts/backup-hook.sh 850 | ``` 851 | 852 |
853 | 854 | ### Clean Command 855 | 856 |
857 | Snapshot Cleanup Options 858 | 859 | ```bash 860 | cv4pve-autosnap [global-options] clean [clean-options] 861 | ``` 862 | 863 | #### Clean-Specific Options 864 | | Parameter | Description | Notes | 865 | |-----------|-------------|-------| 866 | | `--label` | Label to clean | Matches exact label | 867 | | `--keep` | Snapshots to preserve | `0` = remove all | 868 | 869 | #### Examples 870 | ```bash 871 | # Clean old daily snapshots (keep newest 7) 872 | cv4pve-autosnap --host=pve.local --api-token=token --vmid=100 clean --label=daily --keep=7 873 | 874 | # Remove all snapshots with specific label 875 | cv4pve-autosnap --host=pve.local --api-token=token --vmid=100 clean --label=old-backup --keep=0 876 | 877 | # Clean across multiple VMs 878 | cv4pve-autosnap --host=pve.local --api-token=token --vmid=@pool-development clean --label=test --keep=0 879 | ``` 880 | 881 |
882 | 883 | ### Status Command 884 | 885 |
886 | Snapshot Status & Reporting 887 | 888 | ```bash 889 | cv4pve-autosnap [global-options] status [status-options] 890 | ``` 891 | 892 | #### Status Output Formats 893 | ```bash 894 | # Table format (default) 895 | cv4pve-autosnap --host=pve.local --api-token=token --vmid=100 status 896 | 897 | # JSON format for automation 898 | cv4pve-autosnap --host=pve.local --api-token=token --vmid=100 status --output=json 899 | 900 | # Pretty JSON for debugging 901 | cv4pve-autosnap --host=pve.local --api-token=token --vmid=100 status --output=jsonpretty 902 | ``` 903 | 904 | #### Example Output 905 | ``` 906 | ┌──────┬─────┬───────────────────┬──────────────────────────────┬──────────────────────────────┬─────────────────┬─────┐ 907 | │ NODE │ VM │ TIME │ PARENT │ NAME │ DESCRIPTION │ RAM │ 908 | ├──────┼─────┼───────────────────┼──────────────────────────────┼──────────────────────────────┼─────────────────┼─────┤ 909 | │ pve1 │ 100 │ 25/08/11 09:21:35 │ no-parent │ autodaily250811092135 │ cv4pve-autosnap │ │ 910 | │ pve1 │ 100 │ 25/08/11 12:23:09 │ autodaily250811092135 │ autodaily250811122309 │ cv4pve-autosnap │ │ 911 | │ pve1 │ 100 │ 25/08/12 06:50:23 │ autodaily250811122309 │ autodaily250812065023 │ cv4pve-autosnap │ X │ 912 | └──────┴─────┴───────────────────┴──────────────────────────────┴──────────────────────────────┴─────────────────┴─────┘ 913 | ``` 914 | 915 |
916 | 917 | --- 918 | 919 | ## Support 920 | 921 | Professional support and consulting available through [Corsinvest](https://www.corsinvest.it/cv4pve). 922 | 923 | --- 924 | 925 | Part of [cv4pve](https://www.corsinvest.it/cv4pve) suite | Made with ❤️ in Italy by [Corsinvest](https://www.corsinvest.it) 926 | 927 | Copyright © Corsinvest Srl -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | {project} Copyright (C) {year} {fullname} 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------