forked from wubbl0rz/VmChamp
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit fb07743
Showing
15 changed files
with
827 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
bin | ||
obj | ||
build | ||
build_* | ||
.idea | ||
Folder.DotSettings.user |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
public class AppConfig | ||
{ | ||
public string AppDir { get; private set; } | ||
public string CacheDir { get; private set; } | ||
public string DataDir { get; private set; } | ||
public string DefaultVmName { get; set; } = "testvm"; | ||
public string DefaultVmDistro { get; set; } = "Debian11"; | ||
public string DefaultUser { get; set; } = "user"; | ||
|
||
public AppConfig(string appName = "VmChamp", string sessionName = "default") | ||
{ | ||
var homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); | ||
this.AppDir = Path.Combine(homeDir, appName); | ||
this.CacheDir = Path.Combine(this.AppDir, sessionName, "cache"); | ||
this.DataDir = Path.Combine(this.AppDir, sessionName, "vms"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
using System.CommandLine; | ||
using Spectre.Console; | ||
using VmChamp; | ||
|
||
public class CleanCommand : Command | ||
{ | ||
private readonly AppConfig _appConfig; | ||
|
||
public CleanCommand(AppConfig appConfig) : base("clean", "delete all vms and images") | ||
{ | ||
_appConfig = appConfig; | ||
|
||
var allVmDirectories = Directory.GetDirectories(appConfig.DataDir); | ||
|
||
this.SetHandler(() => | ||
{ | ||
AnsiConsole.MarkupLine($"[red]Going to delete all VMs ({allVmDirectories.Length}) and IMAGES[/]"); | ||
|
||
if (AnsiConsole.Ask<string>("Continue? (y/N)", "N").ToLower() != "y") | ||
{ | ||
return; | ||
} | ||
|
||
using var libvirtConnection = LibvirtConnection.Create("qemu:///session"); | ||
|
||
foreach (var vmDir in allVmDirectories) | ||
{ | ||
var vmName = Path.GetFileName(vmDir); | ||
var vmId = Interop.virDomainLookupByName(libvirtConnection.NativePtr, vmName); | ||
|
||
Interop.DestroyVm(vmId, vmName, vmDir); | ||
} | ||
|
||
Directory.Delete(appConfig.CacheDir, true); | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
namespace VmChamp; | ||
|
||
public class DistroInfo | ||
{ | ||
public static IEnumerable<DistroInfo> Distros { get; set; } = new List<DistroInfo>() | ||
{ | ||
new() | ||
{ | ||
Name = "Debian11", | ||
ImageName = "debian-11-genericcloud-amd64.qcow2", | ||
Url = "https://cloud.debian.org/images/cloud/bullseye/latest/", | ||
Aliases = new[] { "Bullseye" } | ||
}, | ||
new() | ||
{ | ||
Name = "Debian10", | ||
ImageName = "debian-10-genericcloud-amd64.qcow2", | ||
Url = "https://cloud.debian.org/images/cloud/buster/latest/", | ||
Aliases = new[] { "Buster" } | ||
}, | ||
new() | ||
{ | ||
Name = "Debian9", | ||
ImageName = "debian-10-genericcloud-amd64.qcow2", | ||
Url = "https://cloud.debian.org/images/cloud/stretch/latest/", | ||
Aliases = new[] { "Stretch" } | ||
} | ||
}; | ||
|
||
public required string Name { get; set; } | ||
public required string ImageName { get; set; } | ||
public required string Url { get; set; } | ||
public required string[] Aliases { get; set; } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
namespace VmChamp; | ||
|
||
using System.Net; | ||
using Spectre.Console; | ||
|
||
public class Downloader | ||
{ | ||
private readonly DirectoryInfo _cacheDirectory; | ||
|
||
public Downloader(DirectoryInfo cacheDirectory) | ||
{ | ||
_cacheDirectory = cacheDirectory; | ||
} | ||
|
||
public async Task<FileInfo> DownloadAsync(DistroInfo distroInfo, bool force = false) | ||
{ | ||
var targetFile = new FileInfo(Path.Combine(_cacheDirectory.FullName, distroInfo.ImageName)); | ||
|
||
if (targetFile.Exists && !force) | ||
{ | ||
AnsiConsole.WriteLine($"Using existing image: {distroInfo.ImageName}"); | ||
return targetFile; | ||
} | ||
|
||
var uri = new Uri($"{distroInfo.Url}/{distroInfo.ImageName}"); | ||
|
||
AnsiConsole.WriteLine($"Download: {uri}"); | ||
|
||
#pragma warning disable SYSLIB0014 | ||
var webClient = new WebClient(); | ||
#pragma warning restore SYSLIB0014 | ||
|
||
await AnsiConsole.Progress() | ||
.Columns(new SpinnerColumn(), new PercentageColumn(), new RemainingTimeColumn()) | ||
.StartAsync(async ctx => | ||
{ | ||
var task = ctx.AddTask($"progress"); | ||
|
||
webClient.DownloadProgressChanged += (_, eventArgs) => | ||
{ | ||
task.Value = eventArgs.ProgressPercentage; | ||
}; | ||
|
||
await webClient.DownloadFileTaskAsync(uri, targetFile.FullName); | ||
}); | ||
|
||
return targetFile; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
using System.Diagnostics; | ||
using Spectre.Console; | ||
|
||
namespace VmChamp; | ||
|
||
public class Helper | ||
{ | ||
public static Process? ConnectViaSsh(string user, string ip) | ||
{ | ||
ProcessStartInfo s = new ProcessStartInfo("ssh"); | ||
s.Arguments = $"-o StrictHostKeyChecking=off {user}@{ip}"; | ||
|
||
return Process.Start(s); | ||
} | ||
|
||
public static void DeleteExistingDirectory(string directoryPath) | ||
{ | ||
if (Directory.Exists(directoryPath)) | ||
{ | ||
Directory.Delete(directoryPath, true); | ||
} | ||
} | ||
|
||
public static string?[] GetAllVmInDirectory(string path) => | ||
Directory.GetDirectories(path) | ||
.Select(Path.GetFileName) | ||
.ToArray(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
using System.Runtime.InteropServices; | ||
using Spectre.Console; | ||
|
||
namespace VmChamp; | ||
|
||
[StructLayout(LayoutKind.Sequential)] | ||
public unsafe struct VirDomainIpAddress | ||
{ | ||
public int type; | ||
[MarshalAs(UnmanagedType.LPUTF8Str)] public byte* addr; | ||
public uint prefix; | ||
} | ||
|
||
[StructLayout(LayoutKind.Sequential)] | ||
public unsafe struct VirDomainInterface | ||
{ | ||
public byte* name; | ||
public byte* hwaddr; | ||
public uint naddrs; | ||
public VirDomainIpAddress* addrs; | ||
} | ||
|
||
public static unsafe class Interop | ||
{ | ||
[DllImport("libvirt.so.0", | ||
CallingConvention = CallingConvention.Cdecl, | ||
EntryPoint = "virDomainInterfaceAddresses")] | ||
public static extern nint virDomainInterfaceAddresses(IntPtr dom, | ||
VirDomainInterface*** ifaces, | ||
uint source, | ||
uint flags = 0); | ||
|
||
[DllImport("libvirt.so.0", | ||
CallingConvention = CallingConvention.Cdecl, | ||
EntryPoint = "virDomainCreateXML")] | ||
public static extern nint virDomainCreateXML(IntPtr conn, | ||
string xmlDesc, | ||
uint flags); | ||
|
||
[DllImport("libvirt.so.0", | ||
CallingConvention = CallingConvention.Cdecl, | ||
EntryPoint = "virConnectOpen")] | ||
public static extern nint virConnectOpen(string name); | ||
|
||
[DllImport("libvirt.so.0", | ||
CallingConvention = CallingConvention.Cdecl, | ||
EntryPoint = "virConnectClose")] | ||
public static extern nint virConnectClose(nint conn); | ||
|
||
[DllImport("libvirt.so.0", | ||
CallingConvention = CallingConvention.Cdecl, | ||
EntryPoint = "virDomainLookupByName")] | ||
public static extern nint virDomainLookupByName(nint conn, string name); | ||
|
||
[DllImport("libvirt.so.0", | ||
CallingConvention = CallingConvention.Cdecl, | ||
EntryPoint = "virDomainDestroy")] | ||
public static extern int virDomainDestroy(nint domain); | ||
|
||
public static string? GetFirstIpById(nint id) | ||
{ | ||
VirDomainInterface** ifaces = null; | ||
|
||
var n = Interop.virDomainInterfaceAddresses(id, &ifaces, 2); | ||
|
||
if (n <= 0 || ifaces == null) | ||
return null; | ||
|
||
var virDomainIpAddress = ifaces[0]->addrs[0]; | ||
var ip = Marshal.PtrToStringUTF8((nint)virDomainIpAddress.addr); | ||
return ip; | ||
} | ||
|
||
public static void DestroyVm(nint vmId, string vmName, string vmDir) | ||
{ | ||
AnsiConsole.MarkupLine($"[yellow]💀 Removing VM: {vmName}[/]"); | ||
|
||
if(Interop.virDomainDestroy(vmId) != 0) | ||
{ | ||
AnsiConsole.MarkupLine($"[red]Error removing VM: {vmName}[/]"); | ||
} | ||
|
||
if (Directory.Exists(vmDir)) | ||
{ | ||
Directory.Delete(vmDir, true); | ||
} | ||
} | ||
} | ||
|
||
public class LibvirtConnection : IDisposable | ||
{ | ||
private readonly nint _ptr; | ||
public nint NativePtr => _ptr; | ||
public bool IsValid => this._ptr != nint.Zero; | ||
|
||
private LibvirtConnection(nint ptr) | ||
{ | ||
_ptr = ptr; | ||
} | ||
|
||
public static LibvirtConnection Create(string target) | ||
{ | ||
var ptr = Interop.virConnectOpen(target); | ||
|
||
if (ptr == nint.Zero) | ||
{ | ||
throw new ArgumentException($"Cannot connect to: {target}"); | ||
} | ||
|
||
return new LibvirtConnection(ptr); | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
if (this.IsValid) | ||
{ | ||
Interop.virConnectClose(_ptr); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
using System.Text; | ||
using DiscUtils.Iso9660; | ||
|
||
public class IsoImager | ||
{ | ||
private IEnumerable<string> FindSshKeys() | ||
{ | ||
var homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); | ||
var keys = new List<string>(); | ||
|
||
foreach (var file in Directory.EnumerateFiles(Path.Combine(homeDir, ".ssh"))) | ||
{ | ||
if (file.EndsWith(".pub")) | ||
{ | ||
keys.Add(File.ReadAllText(file)); | ||
} | ||
} | ||
|
||
return keys; | ||
} | ||
|
||
public FileInfo CreateImage(string hostname, DirectoryInfo outputDirectory) | ||
{ | ||
var keys = "[" + string.Join(",", this.FindSshKeys().Select(key => $"\"{key.Trim()}\"")) + "]"; | ||
|
||
var userData = $""" | ||
Content-Type: multipart/mixed; boundary="==BOUNDARY==" | ||
MIME-Version: 1.0 | ||
--==BOUNDARY== | ||
Content-Type: text/cloud-config; charset="us-ascii" | ||
#cloud-config | ||
preserve_hostname: False | ||
hostname: {hostname} | ||
users: | ||
- default | ||
- name: user | ||
groups: ['sudo'] | ||
shell: /bin/bash | ||
sudo: ALL=(ALL) NOPASSWD:ALL | ||
ssh-authorized-keys: | ||
{keys} | ||
output: | ||
all: ">> /var/log/cloud-init.log" | ||
ssh_genkeytypes: ['ed25519', 'rsa'] | ||
ssh_authorized_keys: | ||
{keys} | ||
runcmd: | ||
- systemctl stop networking && systemctl start networking | ||
- systemctl disable cloud-init.service | ||
--==BOUNDARY==-- | ||
"""; | ||
|
||
var metaData = $""" | ||
instance-id: {hostname} | ||
local-hostname: {hostname} | ||
"""; | ||
|
||
var builder = new CDBuilder(); | ||
builder.UseJoliet = true; | ||
builder.VolumeIdentifier = "cidata"; | ||
builder.AddFile(@"user-data", Encoding.UTF8.GetBytes(userData)); | ||
builder.AddFile(@"meta-data", Encoding.UTF8.GetBytes(metaData)); | ||
|
||
var outputFile = Path.Combine(outputDirectory.FullName, "cloudInit.iso"); | ||
|
||
builder.Build(outputFile); | ||
|
||
return new FileInfo(outputFile); | ||
} | ||
} |
Oops, something went wrong.