From f3ebcd0e6a89822bbfc3b88b8e2f9f983c0a2845 Mon Sep 17 00:00:00 2001 From: "daniele.corsini@corsinvest.it" Date: Thu, 2 May 2024 12:20:45 +0200 Subject: [PATCH] Proxmox VE 8.2 --- .vscode/launch.json | 19 +- .vscode/tasks.json | 13 +- README.md | 24 +- .../Corsinvest.ProxmoxVE.Pepper.csproj | 11 +- src/Corsinvest.ProxmoxVE.Pepper/Program.cs | 220 +++++++++--------- 5 files changed, 134 insertions(+), 153 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index f519908..03ed70a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,29 +1,26 @@ { - // Use IntelliSense to find out which attributes exist for C# debugging - // Use hover for the description of the existing attributes - // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md "version": "0.2.0", "configurations": [ { + // Usare IntelliSense per individuare gli attributi esistenti per il debug C# + // Usa il passaggio del mouse per la descrizione degli attributi esistenti + // Per ulteriori informazioni, visitare https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md "name": ".NET Core Launch (console)", "type": "coreclr", "request": "launch", "preLaunchTask": "build", - // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/src/Corsinvest.ProxmoxVE.Pepper/bin/Debug/net6.0/cv4pve-pepper.dll", - "args": [ - "@TestParm.parm" - ], + // Se i framework di destinazione sono stati modificati, assicurarsi di aggiornare il percorso del programma. + "program": "${workspaceFolder}/src/Corsinvest.ProxmoxVE.Pepper/bin/Debug/net8.0/cv4pve-pepper.dll", + "args": [], "cwd": "${workspaceFolder}/src/Corsinvest.ProxmoxVE.Pepper", - // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + // Per ulteriori informazioni sul campo 'console', vedere https://aka.ms/VSCode-CS-LaunchJson-Console "console": "internalConsole", "stopAtEntry": false }, { "name": ".NET Core Attach", "type": "coreclr", - "request": "attach", - "processId": "${command:pickProcess}" + "request": "attach" } ] } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 2d15f60..7de627f 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -7,9 +7,9 @@ "type": "process", "args": [ "build", - "${workspaceFolder}/src/Corsinvest.ProxmoxVE.Pepper/Corsinvest.ProxmoxVE.Pepper.csproj", + "${workspaceFolder}/Corsinvest.ProxmoxVE.Pepper.sln", "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" + "/consoleloggerparameters:NoSummary;ForceNoAlign" ], "problemMatcher": "$msCompile" }, @@ -19,9 +19,9 @@ "type": "process", "args": [ "publish", - "${workspaceFolder}/src/Corsinvest.ProxmoxVE.Pepper/Corsinvest.ProxmoxVE.Pepper.csproj", + "${workspaceFolder}/Corsinvest.ProxmoxVE.Pepper.sln", "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" + "/consoleloggerparameters:NoSummary;ForceNoAlign" ], "problemMatcher": "$msCompile" }, @@ -32,9 +32,8 @@ "args": [ "watch", "run", - "${workspaceFolder}/src/Corsinvest.ProxmoxVE.Pepper/Corsinvest.ProxmoxVE.Pepper.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" + "--project", + "${workspaceFolder}/Corsinvest.ProxmoxVE.Pepper.sln" ], "problemMatcher": "$msCompile" } diff --git a/README.md b/README.md index 851974c..5b0efcf 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,6 @@ [![License](https://img.shields.io/github/license/Corsinvest/cv4pve-pepper.svg)](LICENSE.md) ```text -Description: - ______ _ __ / ____/___ __________(_)___ _ _____ _____/ /_ / / / __ \/ ___/ ___/ / __ \ | / / _ \/ ___/ __/ @@ -12,33 +10,30 @@ Description: \____/\____/_/ /____/_/_/ /_/|___/\___/____/\__/ - Launching SPICE on Proxmox VE (Made in Italy) + Launching SPICE remote-viewer for Proxmox VE (Made in Italy) cv4pve-pepper is a part of suite cv4pve. - For more information visit https://www.cv4pve-tools.com + For more information visit https://www.corsinvest.it/cv4pve Usage: - cv4pve-pepper [command] [options] + cv4pve-pepper [options] Options: - --host (REQUIRED) The host name host[:port],host1[:port],host2[:port] --api-token Api token format 'USER@REALM!TOKENID=UUID'. Require Proxmox VE 6.2 or later --username User name @ --password The password. Specify 'file:path_file' to store password in file. + --validate-certificate Validate SSL Certificate Proxmox VE node. + --host (REQUIRED) The host name host[:port],host1[:port],host2[:port] --vmid The id or name VM/CT - --proxy SPICE proxy server. This can be used by the client to specify the proxy server. All nodes in a cluster runs - 'spiceproxy', so it is up to the client to choose one. By default, we return the node to connect. If specify - http(s)://[host]:[port] then replace proxy option in file .vv. E.g. for reverse proxy. + --proxy SPICE proxy server. This can be used by the client to specify the proxy server. All nodes in a cluster runs 'spiceproxy', so it is up to + the client to choose one. By default, we return the node to connect. If specify http(s)://[host]:[port] then replace proxy option in file + .vv. E.g. for reverse proxy. --viewer (REQUIRED) Executable SPICE client remote viewer. --viewer-options Send options directly SPICE Viewer (quote value). --start-or-resume Run stopped or paused VM --wait-for-startup Wait sec. for startup VM [default: 5] --version Show version information -?, -h, --help Show help and usage information - -Commands: - app-check-update Check update application - app-upgrade Upgrade application ``` ## Copyright and License @@ -48,7 +43,7 @@ For licensing details please visit [LICENSE.md](LICENSE.md) ## Commercial Support -This software is part of a suite of tools called cv4pve-tools. If you want commercial support, visit the [site](https://www.cv4pve-tools.com) +This software is part of a suite of tools called cv4pve-tools. If you want commercial support, visit the [site](https://www.corisnvest.it/cv4pve) ## Introduction @@ -76,6 +71,7 @@ this software aims to simplify run SPICE client from Proxmox VE using command li * Use Api token --api-token parameter * Send options directly to viewer * Execution with file parameter e.g. @FileParameter.parm +* Validate certificate SSL, default not validate ## Api token diff --git a/src/Corsinvest.ProxmoxVE.Pepper/Corsinvest.ProxmoxVE.Pepper.csproj b/src/Corsinvest.ProxmoxVE.Pepper/Corsinvest.ProxmoxVE.Pepper.csproj index d3abe17..99bb8fd 100644 --- a/src/Corsinvest.ProxmoxVE.Pepper/Corsinvest.ProxmoxVE.Pepper.csproj +++ b/src/Corsinvest.ProxmoxVE.Pepper/Corsinvest.ProxmoxVE.Pepper.csproj @@ -1,8 +1,8 @@  Exe - 1.6.1 - net6.0 + 1.7.0 + net8.0 cv4pve-pepper Corsinvest Srl Daniele Corsini @@ -15,13 +15,8 @@ - - - - - - + diff --git a/src/Corsinvest.ProxmoxVE.Pepper/Program.cs b/src/Corsinvest.ProxmoxVE.Pepper/Program.cs index 95f3d2a..a10af4d 100644 --- a/src/Corsinvest.ProxmoxVE.Pepper/Program.cs +++ b/src/Corsinvest.ProxmoxVE.Pepper/Program.cs @@ -5,143 +5,137 @@ using System; using System.CommandLine; -using System.CommandLine.Invocation; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; using System.Text.RegularExpressions; -using System.Threading.Tasks; using Corsinvest.ProxmoxVE.Api.Extension; using Corsinvest.ProxmoxVE.Api.Extension.Utils; using Corsinvest.ProxmoxVE.Api.Shared.Models.Vm; using Corsinvest.ProxmoxVE.Api.Shell.Helpers; +using Microsoft.Extensions.Logging; -namespace Corsinvest.ProxmoxVE.Pepper +var app = ConsoleHelper.CreateApp("cv4pve-pepper", "Launching SPICE remote-viewer for Proxmox VE"); +var loggerFactory = ConsoleHelper.CreateLoggerFactory(app.GetLogLevelFromDebug()); + +var optVmId = app.VmIdOrNameOption(); + +var optProxy = app.AddOption("--proxy", + @"SPICE proxy server. This can be used by the client to specify the proxy server." + + " All nodes in a cluster runs 'spiceproxy', so it is up to the client to choose one." + + " By default, we return the node to connect." + + " If specify http(s)://[host]:[port] then replace proxy option in file .vv. E.g. for reverse proxy."); + +var optRemoteViewer = app.AddOption("--viewer", "Executable SPICE client remote viewer.") + .AddValidatorExistFile(); +optRemoteViewer.IsRequired = true; + +var optViewerOptions = app.AddOption("--viewer-options", "Send options directly SPICE Viewer (quote value)."); +var optStartOrResume = app.AddOption("--start-or-resume", "Run stopped or paused VM"); + +var optWaitForStartup = app.AddOption("--wait-for-startup", "Wait sec. for startup VM"); +optWaitForStartup.SetDefaultValue(5); + +app.SetHandler(async (ctx) => { - class Program + var client = await app.ClientTryLoginAsync(loggerFactory); + var proxy = ctx.ParseResult.GetValueForOption(optProxy); + if (string.IsNullOrWhiteSpace(proxy)) { proxy = client.Host; } + + var vmId = ctx.ParseResult.GetValueForOption(optVmId); + + var vm = await client.GetVmAsync(vmId); + if (ctx.ParseResult.GetValueForOption(optStartOrResume) && (vm.IsStopped || vm.IsPaused)) { - static async Task Main(string[] args) + var status = vm.IsStopped + ? VmStatus.Start + : VmStatus.Resume; + + if (app.DebugIsActive()) { - var app = ConsoleHelper.CreateApp("cv4pve-pepper", "Launching SPICE on Proxmox VE"); - var optVmId = app.VmIdOrNameOption(); + await Console.Out.WriteLineAsync($"VM is {(vm.IsStopped ? "stopped" : "paused")}. {status} now!"); + } - var optProxy = app.AddOption("--proxy", - @"SPICE proxy server. This can be used by the client to specify the proxy server." + - " All nodes in a cluster runs 'spiceproxy', so it is up to the client to choose one." + - " By default, we return the node to connect." + - " If specify http(s)://[host]:[port] then replace proxy option in file .vv. E.g. for reverse proxy."); + //start VM + var result = await VmHelper.ChangeStatusVmAsync(client, vm.Node, vm.VmType, vm.VmId, status); + await client.WaitForTaskToFinishAsync(result, timeout: ctx.ParseResult.GetValueForOption(optWaitForStartup) * 1000); - var optRemoteViewer = app.AddOption("--viewer", "Executable SPICE client remote viewer.").AddValidatorExistFile(); - optRemoteViewer.IsRequired = true; + //check VM is running + vm = await client.GetVmAsync(vmId); + if (app.DebugIsActive()) { await Console.Out.WriteLineAsync($"VM is {vm.Status}."); } + } - var optViewerOptions = app.AddOption("--viewer-options", "Send options directly SPICE Viewer (quote value)."); - - var optStartOrResume = app.AddOption("--start-or-resume", "Run stopped or paused VM"); - - var optWaitForStartup = app.AddOption("--wait-for-startup", "Wait sec. for startup VM"); - optWaitForStartup.SetDefaultValue(5); - - app.SetHandler(async (InvocationContext ctx) => + var (success, reasonPhrase, content) = await client.Nodes[vm.Node].Qemu[vm.Id].Spiceproxy.GetSpiceFileVVAsync(proxy); + if (success) + { + //proxy force + if (new Regex(@"^(http|https|)://.*$").IsMatch(proxy)) + { + var lines = content.Split("\n"); + for (int i = 0; i < lines.Length; i++) { - var loggerFactory = ConsoleHelper.CreateLoggerFactory(app.GetLogLevelFromDebug()); - var client = await app.ClientTryLogin(loggerFactory); - - var proxy = optProxy.GetValue(); - if (string.IsNullOrWhiteSpace(proxy)) { proxy = client.Host; } - - var vm = await client.GetVm(optVmId.GetValue()); - - if (optStartOrResume.GetValue() && (vm.IsStopped || vm.IsPaused)) + if (lines[i].StartsWith("proxy=")) { - var status = vm.IsStopped ? VmStatus.Start : VmStatus.Resume; - - if (app.DebugIsActive()) - { - await Console.Out.WriteLineAsync($"VM is {(vm.IsStopped ? "stopped" : "paused")}. {status} now!"); - } - - //start VM - var result = await VmHelper.ChangeStatusVm(client, vm.Node, vm.VmType, vm.VmId, status); - await client.WaitForTaskToFinish(result, timeout: optWaitForStartup.GetValue() * 1000); - - //check VM is running - vm = await client.GetVm(optVmId.GetValue()); - if (app.DebugIsActive()) { await Console.Out.WriteLineAsync($"VM is {vm.Status}."); } + lines[i] = $"proxy={proxy}"; + break; } + } + content = string.Join("\n", lines); - var (success, reasonPhrase, content) = await VmHelper.GetQemuSpiceFileVV(client, vm.Node, vm.VmId, proxy); - if (success) - { - //proxy force - if (new Regex(@"^(http|https|)://.*$").IsMatch(proxy)) - { - var lines = content.Split("\n"); - for (int i = 0; i < lines.Length; i++) - { - if (lines[i].StartsWith("proxy=")) - { - lines[i] = $"proxy={proxy}"; - break; - } - } - content = string.Join("\n", lines); + if (app.DebugIsActive()) + { + await Console.Out.WriteLineAsync($"Replace Proxy: {proxy}"); + await Console.Out.WriteLineAsync(content); + } + } - if (app.DebugIsActive()) - { - await Console.Out.WriteLineAsync($"Replace Proxy: {proxy}"); - await Console.Out.WriteLineAsync(content); - } - } + var fileName = Path.GetTempFileName().Replace(".tmp", ".vv"); + File.WriteAllText(fileName, content); + var startInfo = new ProcessStartInfo + { + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardOutput = false, + }; - var fileName = Path.GetTempFileName().Replace(".tmp", ".vv"); - File.WriteAllText(fileName, content); - var startInfo = new ProcessStartInfo - { - UseShellExecute = false, - CreateNoWindow = true, - RedirectStandardOutput = false, - }; + var viewerOptions = ctx.ParseResult.GetValueForOption(optViewerOptions); + var remoteViewer = ctx.ParseResult.GetValueForOption(optRemoteViewer); - var viewerOpts = optViewerOptions.GetValue(); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + startInfo.FileName = "/bin/bash"; + startInfo.Arguments = $"-c \"{remoteViewer} {fileName} {viewerOptions}\""; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + startInfo.FileName = $"\"{remoteViewer}\""; + startInfo.Arguments = $"\"{fileName}\" {viewerOptions}"; + } - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - startInfo.FileName = "/bin/bash"; - startInfo.Arguments = $"-c \"{optRemoteViewer.GetValue()} {fileName} {viewerOpts}\""; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - startInfo.FileName = $"\"{optRemoteViewer.GetValue()}\""; - startInfo.Arguments = $"\"{fileName}\" {viewerOpts}"; - } + var process = new Process + { + StartInfo = startInfo + }; - var process = new Process - { - StartInfo = startInfo - }; + if (app.DebugIsActive()) + { + await Console.Out.WriteLineAsync($"Run FileName: {process.StartInfo.FileName}"); + await Console.Out.WriteLineAsync($"Run Arguments: {process.StartInfo.Arguments}"); + } - if (app.DebugIsActive()) - { - await Console.Out.WriteLineAsync($"Run FileName: {process.StartInfo.FileName}"); - await Console.Out.WriteLineAsync($"Run Arguments: {process.StartInfo.Arguments}"); - } - - if (!app.DryRunIsActive()) - { - process.Start(); - ctx.ExitCode = !process.HasExited || process.ExitCode == 0 - ? 0 - : 1; - } - } - else - { - await Console.Out.WriteLineAsync($"Error: {reasonPhrase}"); - ctx.ExitCode = 1; - } - }); - - return await app.ExecuteApp(args); + if (!app.DryRunIsActive()) + { + process.Start(); + ctx.ExitCode = !process.HasExited || process.ExitCode == 0 + ? 0 + : 1; } } -} + else + { + await Console.Out.WriteLineAsync($"Error: {reasonPhrase}"); + ctx.ExitCode = 1; + } +}); + +return await app.ExecuteAppAsync(args, loggerFactory.CreateLogger(typeof(Program))); \ No newline at end of file