From 8f841403377c36800312bd82216c1d7a5f7a3444 Mon Sep 17 00:00:00 2001 From: Alphaeus Mote Date: Sun, 11 May 2025 09:27:20 -0400 Subject: [PATCH] Add LXC container and TurnKey template support --- Documentation/cmdlets/Get-ProxmoxContainer.md | 118 +++++++ .../cmdlets/Get-ProxmoxTurnKeyTemplate.md | 116 +++++++ Documentation/cmdlets/New-ProxmoxContainer.md | 303 ++++++++++++++++++ .../examples/LXC-Container-Management.ps1 | 79 +++++ Documentation/guides/LXCContainers.md | 172 ++++++++++ Module/PSProxmox.psd1 | 20 +- .../Cmdlets/GetProxmoxContainerCmdlet.cs | 223 +++++++++++++ .../GetProxmoxTurnKeyTemplateCmdlet.cs | 242 ++++++++++++++ .../NewProxmoxContainerBuilderCmdlet.cs | 43 +++ .../Cmdlets/NewProxmoxContainerCmdlet.cs | 298 +++++++++++++++++ .../NewProxmoxContainerFromTurnKeyCmdlet.cs | 277 ++++++++++++++++ .../Cmdlets/RemoveProxmoxContainerCmdlet.cs | 101 ++++++ .../Cmdlets/RestartProxmoxContainerCmdlet.cs | 124 +++++++ .../SaveProxmoxTurnKeyTemplateCmdlet.cs | 206 ++++++++++++ .../Cmdlets/StartProxmoxContainerCmdlet.cs | 110 +++++++ .../Cmdlets/StopProxmoxContainerCmdlet.cs | 139 ++++++++ PSProxmox/Models/ProxmoxContainer.cs | 229 +++++++++++++ PSProxmox/Models/ProxmoxContainerBuilder.cs | 187 +++++++++++ PSProxmox/Models/ProxmoxTurnKeyTemplate.cs | 137 ++++++++ README.md | 58 +++- 20 files changed, 3178 insertions(+), 4 deletions(-) create mode 100644 Documentation/cmdlets/Get-ProxmoxContainer.md create mode 100644 Documentation/cmdlets/Get-ProxmoxTurnKeyTemplate.md create mode 100644 Documentation/cmdlets/New-ProxmoxContainer.md create mode 100644 Documentation/examples/LXC-Container-Management.ps1 create mode 100644 Documentation/guides/LXCContainers.md create mode 100644 PSProxmox/Cmdlets/GetProxmoxContainerCmdlet.cs create mode 100644 PSProxmox/Cmdlets/GetProxmoxTurnKeyTemplateCmdlet.cs create mode 100644 PSProxmox/Cmdlets/NewProxmoxContainerBuilderCmdlet.cs create mode 100644 PSProxmox/Cmdlets/NewProxmoxContainerCmdlet.cs create mode 100644 PSProxmox/Cmdlets/NewProxmoxContainerFromTurnKeyCmdlet.cs create mode 100644 PSProxmox/Cmdlets/RemoveProxmoxContainerCmdlet.cs create mode 100644 PSProxmox/Cmdlets/RestartProxmoxContainerCmdlet.cs create mode 100644 PSProxmox/Cmdlets/SaveProxmoxTurnKeyTemplateCmdlet.cs create mode 100644 PSProxmox/Cmdlets/StartProxmoxContainerCmdlet.cs create mode 100644 PSProxmox/Cmdlets/StopProxmoxContainerCmdlet.cs create mode 100644 PSProxmox/Models/ProxmoxContainer.cs create mode 100644 PSProxmox/Models/ProxmoxContainerBuilder.cs create mode 100644 PSProxmox/Models/ProxmoxTurnKeyTemplate.cs diff --git a/Documentation/cmdlets/Get-ProxmoxContainer.md b/Documentation/cmdlets/Get-ProxmoxContainer.md new file mode 100644 index 0000000..286b0fb --- /dev/null +++ b/Documentation/cmdlets/Get-ProxmoxContainer.md @@ -0,0 +1,118 @@ +# Get-ProxmoxContainer + +## SYNOPSIS +Gets Proxmox LXC containers. + +## SYNTAX + +```powershell +Get-ProxmoxContainer [[-CTID] ] [[-Node] ] [[-Name] ] [] +``` + +## DESCRIPTION +Gets Proxmox LXC containers from a Proxmox server. + +If no parameters are specified, all containers are returned. +If a CTID is specified, only that container is returned. +If a node is specified, only containers on that node are returned. +If a name is specified, only containers with that name are returned (supports wildcards and regex). + +## EXAMPLES + +### Example 1: Get all containers +```powershell +Get-ProxmoxContainer +``` + +Gets all containers from the Proxmox server. + +### Example 2: Get a specific container +```powershell +Get-ProxmoxContainer -CTID 100 +``` + +Gets the container with CTID 100. + +### Example 3: Get all containers on a specific node +```powershell +Get-ProxmoxContainer -Node "pve1" +``` + +Gets all containers on node pve1. + +### Example 4: Get containers by name using wildcards +```powershell +Get-ProxmoxContainer -Name "web*" +``` + +Gets all containers with names starting with "web" (wildcard). + +### Example 5: Get containers by name using regex +```powershell +Get-ProxmoxContainer -Name "^web\d+$" +``` + +Gets all containers with names matching the regex pattern "^web\d+$". + +## PARAMETERS + +### -CTID +The container ID. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: 0 +Default value: None +Accept pipeline input: True (ByValue, ByPropertyName) +Accept wildcard characters: False +``` + +### -Node +The node name. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: 1 +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Name +The container name (supports wildcards and regex). + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: 2 +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: True +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### System.Int32 +### System.String + +## OUTPUTS + +### PSProxmox.Models.ProxmoxContainer + +## NOTES + +## RELATED LINKS diff --git a/Documentation/cmdlets/Get-ProxmoxTurnKeyTemplate.md b/Documentation/cmdlets/Get-ProxmoxTurnKeyTemplate.md new file mode 100644 index 0000000..3410be5 --- /dev/null +++ b/Documentation/cmdlets/Get-ProxmoxTurnKeyTemplate.md @@ -0,0 +1,116 @@ +# Get-ProxmoxTurnKeyTemplate + +## SYNOPSIS +Gets TurnKey Linux templates for Proxmox LXC containers. + +## SYNTAX + +```powershell +Get-ProxmoxTurnKeyTemplate [[-Node] ] [[-Name] ] [-IncludeDownloaded] [] +``` + +## DESCRIPTION +Gets TurnKey Linux templates for Proxmox LXC containers from a Proxmox server. + +If no parameters are specified, all templates are returned. +If a node is specified, only templates available on that node are returned. +If a name is specified, only templates with that name are returned (supports wildcards and regex). + +## EXAMPLES + +### Example 1: Get all TurnKey templates +```powershell +Get-ProxmoxTurnKeyTemplate +``` + +Gets all TurnKey Linux templates from the Proxmox server. + +### Example 2: Get all TurnKey templates on a specific node +```powershell +Get-ProxmoxTurnKeyTemplate -Node "pve1" +``` + +Gets all TurnKey Linux templates available on node pve1. + +### Example 3: Get TurnKey templates by name using wildcards +```powershell +Get-ProxmoxTurnKeyTemplate -Name "wordpress*" +``` + +Gets all TurnKey Linux templates with names starting with "wordpress" (wildcard). + +### Example 4: Get TurnKey templates by name using regex +```powershell +Get-ProxmoxTurnKeyTemplate -Name "^wordpress\d+$" +``` + +Gets all TurnKey Linux templates with names matching the regex pattern "^wordpress\d+$". + +### Example 5: Include downloaded templates +```powershell +Get-ProxmoxTurnKeyTemplate -IncludeDownloaded +``` + +Gets all TurnKey Linux templates, including those that have already been downloaded. + +## PARAMETERS + +### -Node +The node name. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: 0 +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Name +The template name (supports wildcards and regex). + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: 1 +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: True +``` + +### -IncludeDownloaded +Whether to include downloaded templates. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### System.String + +## OUTPUTS + +### PSProxmox.Models.ProxmoxTurnKeyTemplate + +## NOTES + +## RELATED LINKS diff --git a/Documentation/cmdlets/New-ProxmoxContainer.md b/Documentation/cmdlets/New-ProxmoxContainer.md new file mode 100644 index 0000000..71982ae --- /dev/null +++ b/Documentation/cmdlets/New-ProxmoxContainer.md @@ -0,0 +1,303 @@ +# New-ProxmoxContainer + +## SYNOPSIS +Creates a new Proxmox LXC container. + +## SYNTAX + +```powershell +New-ProxmoxContainer -Node [-Builder ] [-CTID ] [-Name ] + [-OSTemplate ] [-Storage ] [-Memory ] [-Swap ] [-Cores ] + [-DiskSize ] [-Unprivileged] [-Password ] [-SSHKey ] [-Description ] + [-StartOnBoot] [-Start] [] +``` + +## DESCRIPTION +Creates a new Proxmox LXC container on a Proxmox server. + +## EXAMPLES + +### Example 1: Create a new container +```powershell +New-ProxmoxContainer -Node "pve1" -Name "web-container" -OSTemplate "local:vztmpl/ubuntu-20.04-standard_20.04-1_amd64.tar.gz" -Storage "local-lvm" -Memory 512 -Swap 512 -Cores 1 -DiskSize 8 +``` + +Creates a new LXC container on node pve1. + +### Example 2: Create a new container using a builder +```powershell +$builder = New-ProxmoxContainerBuilder -Name "web-container" +$builder.WithOSTemplate("local:vztmpl/ubuntu-20.04-standard_20.04-1_amd64.tar.gz") + .WithStorage("local-lvm") + .WithMemory(512) + .WithSwap(512) + .WithCores(1) + .WithDiskSize(8) + .WithUnprivileged($true) + .WithStartOnBoot($true) + .WithStart($true) +New-ProxmoxContainer -Node "pve1" -Builder $builder +``` + +Creates a new LXC container on node pve1 using a builder. + +## PARAMETERS + +### -Node +The node name. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: True +Position: 0 +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Builder +The container builder. + +```yaml +Type: ProxmoxContainerBuilder +Parameter Sets: (All) +Aliases: + +Required: False +Position: 1 +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + +### -CTID +The container ID. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Name +The container name. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -OSTemplate +The container OS template. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Storage +The container storage. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Memory +The container memory limit in MB. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Swap +The container swap limit in MB. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Cores +The container CPU cores. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -DiskSize +The container disk size in GB. + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Unprivileged +Whether the container is unprivileged. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Password +The container password. + +```yaml +Type: SecureString +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -SSHKey +The container SSH public key. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Description +The container description. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -StartOnBoot +Whether to start the container on boot. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Start +Whether to start the container after creation. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### System.String +### PSProxmox.Models.ProxmoxContainerBuilder +### System.Int32 +### System.Security.SecureString +### System.Management.Automation.SwitchParameter + +## OUTPUTS + +### PSProxmox.Models.ProxmoxContainer + +## NOTES + +## RELATED LINKS diff --git a/Documentation/examples/LXC-Container-Management.ps1 b/Documentation/examples/LXC-Container-Management.ps1 new file mode 100644 index 0000000..43ddc9b --- /dev/null +++ b/Documentation/examples/LXC-Container-Management.ps1 @@ -0,0 +1,79 @@ +# LXC Container Management Examples + +# Connect to the Proxmox server +$securePassword = ConvertTo-SecureString "password" -AsPlainText -Force +Connect-ProxmoxServer -Server "proxmox.example.com" -Username "root" -Password $securePassword -Realm "pam" + +# Get all LXC containers +$containers = Get-ProxmoxContainer +$containers | Format-Table -Property CTID, Name, Status, Node + +# Get a specific LXC container +$container = Get-ProxmoxContainer -CTID 100 +$container + +# Get all LXC containers on a specific node +$nodeContainers = Get-ProxmoxContainer -Node "pve1" +$nodeContainers | Format-Table -Property CTID, Name, Status + +# Get LXC containers by name pattern (wildcard) +$webContainers = Get-ProxmoxContainer -Name "web*" +$webContainers | Format-Table -Property CTID, Name, Status, Node + +# Get LXC containers by name pattern (regex) +$dbContainers = Get-ProxmoxContainer -Name "^db\d+$" +$dbContainers | Format-Table -Property CTID, Name, Status, Node + +# Create a new LXC container using parameters +$container = New-ProxmoxContainer -Node "pve1" -Name "web-container" -OSTemplate "local:vztmpl/ubuntu-20.04-standard_20.04-1_amd64.tar.gz" -Storage "local-lvm" -Memory 512 -Swap 512 -Cores 1 -DiskSize 8 -Unprivileged -StartOnBoot -Start +$container + +# Create a new LXC container using a builder +$builder = New-ProxmoxContainerBuilder -Name "db-container" +$builder.WithOSTemplate("local:vztmpl/ubuntu-20.04-standard_20.04-1_amd64.tar.gz") + .WithStorage("local-lvm") + .WithMemory(1024) + .WithSwap(1024) + .WithCores(2) + .WithDiskSize(16) + .WithUnprivileged($true) + .WithStartOnBoot($true) + .WithStart($true) + .WithDescription("Database container") + +$container = New-ProxmoxContainer -Node "pve1" -Builder $builder +$container + +# Start an LXC container +Start-ProxmoxContainer -Node "pve1" -CTID 100 -Wait +Get-ProxmoxContainer -CTID 100 + +# Stop an LXC container +Stop-ProxmoxContainer -Node "pve1" -CTID 100 -Wait +Get-ProxmoxContainer -CTID 100 + +# Restart an LXC container +Restart-ProxmoxContainer -Node "pve1" -CTID 100 -Wait +Get-ProxmoxContainer -CTID 100 + +# Remove an LXC container +Remove-ProxmoxContainer -Node "pve1" -CTID 100 -Confirm:$false + +# Get all TurnKey Linux templates +$templates = Get-ProxmoxTurnKeyTemplate +$templates | Format-Table -Property Name, Title, OS, Version, HumanSize + +# Get TurnKey Linux templates by name pattern (wildcard) +$wordpressTemplates = Get-ProxmoxTurnKeyTemplate -Name "wordpress*" +$wordpressTemplates | Format-Table -Property Name, Title, OS, Version, HumanSize + +# Download a TurnKey Linux template +$templatePath = Save-ProxmoxTurnKeyTemplate -Node "pve1" -Name "wordpress" -Storage "local" +$templatePath + +# Create a new LXC container from a TurnKey Linux template +$container = New-ProxmoxContainerFromTurnKey -Node "pve1" -Name "wordpress" -Template "wordpress" -Storage "local-lvm" -Memory 512 -Cores 1 -DiskSize 8 -Start +$container + +# Disconnect from the Proxmox server +Disconnect-ProxmoxServer diff --git a/Documentation/guides/LXCContainers.md b/Documentation/guides/LXCContainers.md new file mode 100644 index 0000000..93e41bc --- /dev/null +++ b/Documentation/guides/LXCContainers.md @@ -0,0 +1,172 @@ +# LXC Container Management Guide + +This guide covers how to use PSProxmox to manage LXC containers on a Proxmox VE server. + +## Overview + +Proxmox VE supports Linux Containers (LXC) as a lightweight alternative to full virtual machines. LXC containers provide an isolated environment for applications with minimal overhead compared to VMs. + +PSProxmox provides cmdlets for creating, managing, and removing LXC containers, as well as working with TurnKey Linux templates. + +## Container Management Cmdlets + +PSProxmox includes the following cmdlets for managing LXC containers: + +- `Get-ProxmoxContainer`: List all LXC containers or get details for a specific container +- `New-ProxmoxContainer`: Create a new LXC container +- `New-ProxmoxContainerBuilder`: Create a builder for configuring container parameters +- `Remove-ProxmoxContainer`: Delete an LXC container +- `Start-ProxmoxContainer`: Start an LXC container +- `Stop-ProxmoxContainer`: Stop an LXC container +- `Restart-ProxmoxContainer`: Restart an LXC container + +## TurnKey Template Management Cmdlets + +PSProxmox also includes cmdlets for working with TurnKey Linux templates: + +- `Get-ProxmoxTurnKeyTemplate`: List available TurnKey templates +- `Save-ProxmoxTurnKeyTemplate`: Download a TurnKey template to a Proxmox storage +- `New-ProxmoxContainerFromTurnKey`: Create a new LXC container from a TurnKey template + +## Basic Container Operations + +### Listing Containers + +To list all containers on a Proxmox server: + +```powershell +Get-ProxmoxContainer +``` + +To get a specific container by ID: + +```powershell +Get-ProxmoxContainer -CTID 100 +``` + +To get containers on a specific node: + +```powershell +Get-ProxmoxContainer -Node "pve1" +``` + +To get containers by name pattern (supports wildcards and regex): + +```powershell +# Using wildcards +Get-ProxmoxContainer -Name "web*" + +# Using regex +Get-ProxmoxContainer -Name "^web\d+$" +``` + +### Creating Containers + +To create a new container using parameters: + +```powershell +New-ProxmoxContainer -Node "pve1" -Name "web-container" -OSTemplate "local:vztmpl/ubuntu-20.04-standard_20.04-1_amd64.tar.gz" -Storage "local-lvm" -Memory 512 -Swap 512 -Cores 1 -DiskSize 8 -Unprivileged -StartOnBoot -Start +``` + +To create a new container using a builder (for more complex configurations): + +```powershell +$builder = New-ProxmoxContainerBuilder -Name "db-container" +$builder.WithOSTemplate("local:vztmpl/ubuntu-20.04-standard_20.04-1_amd64.tar.gz") + .WithStorage("local-lvm") + .WithMemory(1024) + .WithSwap(1024) + .WithCores(2) + .WithDiskSize(16) + .WithUnprivileged($true) + .WithStartOnBoot($true) + .WithStart($true) + .WithDescription("Database container") + +New-ProxmoxContainer -Node "pve1" -Builder $builder +``` + +### Managing Container State + +To start a container: + +```powershell +Start-ProxmoxContainer -Node "pve1" -CTID 100 -Wait +``` + +To stop a container: + +```powershell +Stop-ProxmoxContainer -Node "pve1" -CTID 100 -Wait +``` + +To restart a container: + +```powershell +Restart-ProxmoxContainer -Node "pve1" -CTID 100 -Wait +``` + +### Removing Containers + +To remove a container: + +```powershell +Remove-ProxmoxContainer -Node "pve1" -CTID 100 -Confirm:$false +``` + +## Working with TurnKey Linux Templates + +TurnKey Linux provides pre-built appliances that can be used as templates for LXC containers. + +### Listing TurnKey Templates + +To list all available TurnKey templates: + +```powershell +Get-ProxmoxTurnKeyTemplate +``` + +To list templates by name pattern: + +```powershell +Get-ProxmoxTurnKeyTemplate -Name "wordpress*" +``` + +To include templates that have already been downloaded: + +```powershell +Get-ProxmoxTurnKeyTemplate -IncludeDownloaded +``` + +### Downloading TurnKey Templates + +To download a TurnKey template to a storage: + +```powershell +Save-ProxmoxTurnKeyTemplate -Node "pve1" -Name "wordpress" -Storage "local" +``` + +### Creating Containers from TurnKey Templates + +To create a container from a TurnKey template: + +```powershell +New-ProxmoxContainerFromTurnKey -Node "pve1" -Name "wordpress" -Template "wordpress" -Storage "local-lvm" -Memory 512 -Cores 1 -DiskSize 8 -Start +``` + +## Best Practices + +1. **Use Unprivileged Containers**: For better security, create unprivileged containers when possible. +2. **Resource Allocation**: Allocate appropriate resources (memory, CPU, disk) based on the container's purpose. +3. **Storage Selection**: Choose the appropriate storage type for your containers (local-lvm is often a good choice for performance). +4. **Naming Convention**: Use a consistent naming convention for your containers to make management easier. +5. **Template Management**: Download and maintain templates on a central storage for easy access from all nodes. + +## Examples + +See the [LXC Container Management Examples](../examples/LXC-Container-Management.ps1) for more detailed examples. + +## Related Documentation + +- [Proxmox VE LXC Documentation](https://pve.proxmox.com/wiki/Linux_Container) +- [TurnKey Linux](https://www.turnkeylinux.org/) diff --git a/Module/PSProxmox.psd1 b/Module/PSProxmox.psd1 index 89bdafe..bde8f73 100644 --- a/Module/PSProxmox.psd1 +++ b/Module/PSProxmox.psd1 @@ -1,5 +1,5 @@ @{ - ModuleVersion = '2025.05.10.1500' + ModuleVersion = '2025.05.10.1600' GUID = 'd24f0894-3d0c-4ef1-a41e-b273c3db86ad' Author = 'PSProxmox Team' CompanyName = 'PSProxmox' @@ -83,7 +83,21 @@ 'Save-ProxmoxCloudImage', 'Invoke-ProxmoxCloudImageCustomization', 'New-ProxmoxCloudImageTemplate', - 'Set-ProxmoxVMCloudInit' + 'Set-ProxmoxVMCloudInit', + + # LXC Container Management + 'Get-ProxmoxContainer', + 'New-ProxmoxContainer', + 'New-ProxmoxContainerBuilder', + 'Remove-ProxmoxContainer', + 'Start-ProxmoxContainer', + 'Stop-ProxmoxContainer', + 'Restart-ProxmoxContainer', + + # TurnKey Template Management + 'Get-ProxmoxTurnKeyTemplate', + 'Save-ProxmoxTurnKeyTemplate', + 'New-ProxmoxContainerFromTurnKey' ) VariablesToExport = @() AliasesToExport = @() @@ -92,7 +106,7 @@ Tags = @('Proxmox', 'VirtualMachine', 'Cluster', 'Management') LicenseUri = 'https://github.com/Grace-Solutions/PSProxmox/blob/main/LICENSE' ProjectUri = 'https://github.com/Grace-Solutions/PSProxmox' - ReleaseNotes = 'Added cloud image template functionality. See https://github.com/Grace-Solutions/PSProxmox/releases/tag/v2025.05.10.1500' + ReleaseNotes = 'Added LXC container and TurnKey template support. See https://github.com/Grace-Solutions/PSProxmox/releases/tag/v2025.05.10.1600' } } } diff --git a/PSProxmox/Cmdlets/GetProxmoxContainerCmdlet.cs b/PSProxmox/Cmdlets/GetProxmoxContainerCmdlet.cs new file mode 100644 index 0000000..8aa73dd --- /dev/null +++ b/PSProxmox/Cmdlets/GetProxmoxContainerCmdlet.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; +using System.Text.RegularExpressions; +using Newtonsoft.Json.Linq; +using PSProxmox.Models; +using PSProxmox.Session; + +namespace PSProxmox.Cmdlets +{ + /// + /// Gets Proxmox LXC containers + /// Gets Proxmox LXC containers from a Proxmox server + /// If no parameters are specified, all containers are returned + /// If a CTID is specified, only that container is returned + /// If a node is specified, only containers on that node are returned + /// If a name is specified, only containers with that name are returned (supports wildcards and regex) + /// + /// + /// Get-ProxmoxContainer + /// Gets all containers from the Proxmox server + /// + /// + /// Get-ProxmoxContainer -CTID 100 + /// Gets the container with CTID 100 + /// + /// + /// Get-ProxmoxContainer -Node "pve1" + /// Gets all containers on node pve1 + /// + /// + /// Get-ProxmoxContainer -Name "web*" + /// Gets all containers with names starting with "web" (wildcard) + /// + /// + /// Get-ProxmoxContainer -Name "^web\d+$" + /// Gets all containers with names matching the regex pattern "^web\d+$" + /// + [Cmdlet(VerbsCommon.Get, "ProxmoxContainer")] + [OutputType(typeof(ProxmoxContainer))] + public class GetProxmoxContainerCmdlet : ProxmoxCmdlet + { + /// + /// Gets or sets the container ID + /// + [Parameter(Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] + public int? CTID { get; set; } + + /// + /// Gets or sets the node name + /// + [Parameter(Position = 1, ValueFromPipelineByPropertyName = true)] + public string Node { get; set; } + + /// + /// Gets or sets the container name (supports wildcards and regex) + /// + [Parameter(Position = 2, ValueFromPipelineByPropertyName = true)] + public string Name { get; set; } + + /// + /// Processes the cmdlet + /// + protected override void ProcessRecord() + { + base.ProcessRecord(); + + try + { + if (CTID.HasValue && !string.IsNullOrEmpty(Node)) + { + // Get a specific container on a specific node + var container = GetContainer(Node, CTID.Value); + if (container != null) + { + WriteObject(container); + } + } + else if (CTID.HasValue) + { + // Get a specific container on any node + var nodes = GetNodes(); + foreach (var node in nodes) + { + var container = GetContainer(node.Name, CTID.Value); + if (container != null) + { + WriteObject(container); + break; + } + } + } + else if (!string.IsNullOrEmpty(Node)) + { + // Get all containers on a specific node + var containers = GetContainers(Node); + + // Filter by name if specified + if (!string.IsNullOrEmpty(Name)) + { + containers = FilterContainersByName(containers, Name); + } + + WriteObject(containers, true); + } + else + { + // Get all containers on all nodes + var nodes = GetNodes(); + var allContainers = new List(); + + foreach (var node in nodes) + { + var containers = GetContainers(node.Name); + allContainers.AddRange(containers); + } + + // Filter by name if specified + if (!string.IsNullOrEmpty(Name)) + { + allContainers = FilterContainersByName(allContainers, Name); + } + + WriteObject(allContainers, true); + } + } + catch (Exception ex) + { + WriteError(new ErrorRecord(ex, "GetProxmoxContainerError", ErrorCategory.OperationStopped, null)); + } + } + + private List GetNodes() + { + var response = Connection.GetJson("/nodes"); + var data = response["data"]; + + var nodes = new List(); + foreach (var item in data) + { + var node = item.ToObject(); + nodes.Add(node); + } + + return nodes; + } + + private List GetContainers(string node) + { + var response = Connection.GetJson($"/nodes/{node}/lxc"); + var data = response["data"]; + + var containers = new List(); + foreach (var item in data) + { + var container = item.ToObject(); + container.Node = node; + containers.Add(container); + } + + return containers; + } + + private ProxmoxContainer GetContainer(string node, int ctid) + { + try + { + var response = Connection.GetJson($"/nodes/{node}/lxc/{ctid}/status/current"); + var data = response["data"]; + + var container = data.ToObject(); + container.Node = node; + container.CTID = ctid; + + // Get additional configuration + var configResponse = Connection.GetJson($"/nodes/{node}/lxc/{ctid}/config"); + var configData = configResponse["data"]; + + container.Config = configData.ToObject>(); + + return container; + } + catch (Exception) + { + // Container not found on this node + return null; + } + } + + private List FilterContainersByName(List containers, string namePattern) + { + // Check if the pattern is a regex + if (IsRegexPattern(namePattern)) + { + var regex = new Regex(namePattern); + return containers.Where(c => regex.IsMatch(c.Name)).ToList(); + } + + // Otherwise, treat it as a wildcard pattern + return containers.Where(c => IsWildcardMatch(c.Name, namePattern)).ToList(); + } + + private bool IsRegexPattern(string pattern) + { + // Simple heuristic: if the pattern contains regex-specific characters, treat it as regex + return pattern.StartsWith("^") || pattern.EndsWith("$") || + pattern.Contains("(") || pattern.Contains(")") || + pattern.Contains("[") || pattern.Contains("]") || + pattern.Contains("\\"); + } + + private bool IsWildcardMatch(string input, string pattern) + { + // Convert wildcard pattern to regex + string regexPattern = "^" + Regex.Escape(pattern) + .Replace("\\*", ".*") + .Replace("\\?", ".") + "$"; + + return Regex.IsMatch(input, regexPattern, RegexOptions.IgnoreCase); + } + } +} diff --git a/PSProxmox/Cmdlets/GetProxmoxTurnKeyTemplateCmdlet.cs b/PSProxmox/Cmdlets/GetProxmoxTurnKeyTemplateCmdlet.cs new file mode 100644 index 0000000..4e44376 --- /dev/null +++ b/PSProxmox/Cmdlets/GetProxmoxTurnKeyTemplateCmdlet.cs @@ -0,0 +1,242 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; +using System.Text.RegularExpressions; +using Newtonsoft.Json.Linq; +using PSProxmox.Models; +using PSProxmox.Session; + +namespace PSProxmox.Cmdlets +{ + /// + /// Gets TurnKey Linux templates for Proxmox LXC containers + /// Gets TurnKey Linux templates for Proxmox LXC containers from a Proxmox server + /// If no parameters are specified, all templates are returned + /// If a node is specified, only templates available on that node are returned + /// If a name is specified, only templates with that name are returned (supports wildcards and regex) + /// + /// + /// Get-ProxmoxTurnKeyTemplate + /// Gets all TurnKey Linux templates from the Proxmox server + /// + /// + /// Get-ProxmoxTurnKeyTemplate -Node "pve1" + /// Gets all TurnKey Linux templates available on node pve1 + /// + /// + /// Get-ProxmoxTurnKeyTemplate -Name "wordpress*" + /// Gets all TurnKey Linux templates with names starting with "wordpress" (wildcard) + /// + /// + /// Get-ProxmoxTurnKeyTemplate -Name "^wordpress\d+$" + /// Gets all TurnKey Linux templates with names matching the regex pattern "^wordpress\d+$" + /// + [Cmdlet(VerbsCommon.Get, "ProxmoxTurnKeyTemplate")] + [OutputType(typeof(ProxmoxTurnKeyTemplate))] + public class GetProxmoxTurnKeyTemplateCmdlet : ProxmoxCmdlet + { + /// + /// Gets or sets the node name + /// + [Parameter(Position = 0, ValueFromPipelineByPropertyName = true)] + public string Node { get; set; } + + /// + /// Gets or sets the template name (supports wildcards and regex) + /// + [Parameter(Position = 1, ValueFromPipelineByPropertyName = true)] + public string Name { get; set; } + + /// + /// Gets or sets a value indicating whether to include downloaded templates + /// + [Parameter(Mandatory = false)] + public SwitchParameter IncludeDownloaded { get; set; } + + /// + /// Processes the cmdlet + /// + protected override void ProcessRecord() + { + base.ProcessRecord(); + + try + { + if (string.IsNullOrEmpty(Node)) + { + // Get all nodes + var nodes = GetNodes(); + var allTemplates = new List(); + + foreach (var node in nodes) + { + var templates = GetTemplates(node.Name); + allTemplates.AddRange(templates); + } + + // Remove duplicates + allTemplates = allTemplates.GroupBy(t => t.Name).Select(g => g.First()).ToList(); + + // Filter by name if specified + if (!string.IsNullOrEmpty(Name)) + { + allTemplates = FilterTemplatesByName(allTemplates, Name); + } + + WriteObject(allTemplates, true); + } + else + { + // Get templates for the specified node + var templates = GetTemplates(Node); + + // Filter by name if specified + if (!string.IsNullOrEmpty(Name)) + { + templates = FilterTemplatesByName(templates, Name); + } + + WriteObject(templates, true); + } + } + catch (Exception ex) + { + WriteError(new ErrorRecord(ex, "GetProxmoxTurnKeyTemplateError", ErrorCategory.OperationStopped, null)); + } + } + + private List GetNodes() + { + var response = Connection.GetJson("/nodes"); + var data = response["data"]; + + var nodes = new List(); + foreach (var item in data) + { + var node = item.ToObject(); + nodes.Add(node); + } + + return nodes; + } + + private List GetTemplates(string node) + { + var templates = new List(); + + try + { + // Get available templates from the appliance info + var response = Connection.GetJson($"/nodes/{node}/aplinfo"); + var data = response["data"]; + + foreach (var item in data) + { + // Only include TurnKey templates + if (item["package"] != null && item["package"].ToString().Contains("turnkey")) + { + var template = item.ToObject(); + templates.Add(template); + } + } + + // Get downloaded templates if requested + if (IncludeDownloaded.IsPresent) + { + var storages = GetStorages(node); + foreach (var storage in storages) + { + try + { + var contentResponse = Connection.GetJson($"/nodes/{node}/storage/{storage.Storage}/content"); + var contentData = contentResponse["data"]; + + foreach (var item in contentData) + { + if (item["content"] != null && item["content"].ToString() == "vztmpl" && + item["volid"] != null && item["volid"].ToString().Contains("turnkey")) + { + var volid = item["volid"].ToString(); + var name = System.IO.Path.GetFileNameWithoutExtension(volid.Split('/').Last()); + + // Check if this template is already in the list + if (!templates.Any(t => t.Name == name)) + { + var template = new ProxmoxTurnKeyTemplate + { + Name = name, + Title = name, + Description = "Downloaded template", + Source = storage.Storage, + Location = volid + }; + + templates.Add(template); + } + } + } + } + catch (Exception) + { + // Skip this storage if there's an error + } + } + } + } + catch (Exception) + { + // Skip this node if there's an error + } + + return templates; + } + + private List GetStorages(string node) + { + var response = Connection.GetJson($"/nodes/{node}/storage"); + var data = response["data"]; + + var storages = new List(); + foreach (var item in data) + { + var storage = item.ToObject(); + storages.Add(storage); + } + + return storages; + } + + private List FilterTemplatesByName(List templates, string namePattern) + { + // Check if the pattern is a regex + if (IsRegexPattern(namePattern)) + { + var regex = new Regex(namePattern); + return templates.Where(t => regex.IsMatch(t.Name)).ToList(); + } + + // Otherwise, treat it as a wildcard pattern + return templates.Where(t => IsWildcardMatch(t.Name, namePattern)).ToList(); + } + + private bool IsRegexPattern(string pattern) + { + // Simple heuristic: if the pattern contains regex-specific characters, treat it as regex + return pattern.StartsWith("^") || pattern.EndsWith("$") || + pattern.Contains("(") || pattern.Contains(")") || + pattern.Contains("[") || pattern.Contains("]") || + pattern.Contains("\\"); + } + + private bool IsWildcardMatch(string input, string pattern) + { + // Convert wildcard pattern to regex + string regexPattern = "^" + Regex.Escape(pattern) + .Replace("\\*", ".*") + .Replace("\\?", ".") + "$"; + + return Regex.IsMatch(input, regexPattern, RegexOptions.IgnoreCase); + } + } +} diff --git a/PSProxmox/Cmdlets/NewProxmoxContainerBuilderCmdlet.cs b/PSProxmox/Cmdlets/NewProxmoxContainerBuilderCmdlet.cs new file mode 100644 index 0000000..8bdd9fd --- /dev/null +++ b/PSProxmox/Cmdlets/NewProxmoxContainerBuilderCmdlet.cs @@ -0,0 +1,43 @@ +using System.Management.Automation; +using PSProxmox.Models; + +namespace PSProxmox.Cmdlets +{ + /// + /// Creates a new Proxmox container builder + /// Creates a new Proxmox container builder for creating LXC containers + /// + /// + /// $builder = New-ProxmoxContainerBuilder -Name "web-container" + /// $builder.WithOSTemplate("local:vztmpl/ubuntu-20.04-standard_20.04-1_amd64.tar.gz") + /// .WithStorage("local-lvm") + /// .WithMemory(512) + /// .WithSwap(512) + /// .WithCores(1) + /// .WithDiskSize(8) + /// .WithUnprivileged($true) + /// .WithStartOnBoot($true) + /// .WithStart($true) + /// New-ProxmoxContainer -Node "pve1" -Builder $builder + /// Creates a new container builder and uses it to create a container + /// + [Cmdlet(VerbsCommon.New, "ProxmoxContainerBuilder")] + [OutputType(typeof(ProxmoxContainerBuilder))] + public class NewProxmoxContainerBuilderCmdlet : PSCmdlet + { + /// + /// Gets or sets the container name + /// + [Parameter(Mandatory = true, Position = 0)] + public string Name { get; set; } + + /// + /// Processes the cmdlet + /// + protected override void ProcessRecord() + { + var builder = new ProxmoxContainerBuilder(Name); + WriteObject(builder); + } + } +} diff --git a/PSProxmox/Cmdlets/NewProxmoxContainerCmdlet.cs b/PSProxmox/Cmdlets/NewProxmoxContainerCmdlet.cs new file mode 100644 index 0000000..e211e88 --- /dev/null +++ b/PSProxmox/Cmdlets/NewProxmoxContainerCmdlet.cs @@ -0,0 +1,298 @@ +using System; +using System.Collections.Generic; +using System.Management.Automation; +using System.Security; +using PSProxmox.Models; +using PSProxmox.Session; + +namespace PSProxmox.Cmdlets +{ + /// + /// Creates a new Proxmox LXC container + /// Creates a new Proxmox LXC container on a Proxmox server + /// + /// + /// New-ProxmoxContainer -Node "pve1" -Name "web-container" -OSTemplate "local:vztmpl/ubuntu-20.04-standard_20.04-1_amd64.tar.gz" -Storage "local-lvm" -Memory 512 -Swap 512 -Cores 1 -DiskSize 8 + /// Creates a new LXC container on node pve1 + /// + /// + /// $builder = New-ProxmoxContainerBuilder -Name "web-container" + /// $builder.WithOSTemplate("local:vztmpl/ubuntu-20.04-standard_20.04-1_amd64.tar.gz") + /// .WithStorage("local-lvm") + /// .WithMemory(512) + /// .WithSwap(512) + /// .WithCores(1) + /// .WithDiskSize(8) + /// .WithUnprivileged($true) + /// .WithStartOnBoot($true) + /// .WithStart($true) + /// New-ProxmoxContainer -Node "pve1" -Builder $builder + /// Creates a new LXC container on node pve1 using a builder + /// + [Cmdlet(VerbsCommon.New, "ProxmoxContainer")] + [OutputType(typeof(ProxmoxContainer))] + public class NewProxmoxContainerCmdlet : ProxmoxCmdlet + { + /// + /// Gets or sets the node name + /// + [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)] + public string Node { get; set; } + + /// + /// Gets or sets the container builder + /// + [Parameter(Mandatory = false, Position = 1, ValueFromPipeline = true)] + public ProxmoxContainerBuilder Builder { get; set; } + + /// + /// Gets or sets the container ID + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + public int? CTID { get; set; } + + /// + /// Gets or sets the container name + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + public string Name { get; set; } + + /// + /// Gets or sets the container OS template + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + public string OSTemplate { get; set; } + + /// + /// Gets or sets the container storage + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + public string Storage { get; set; } + + /// + /// Gets or sets the container memory limit in MB + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + public int? Memory { get; set; } + + /// + /// Gets or sets the container swap limit in MB + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + public int? Swap { get; set; } + + /// + /// Gets or sets the container CPU cores + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + public int? Cores { get; set; } + + /// + /// Gets or sets the container disk size in GB + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + public int? DiskSize { get; set; } + + /// + /// Gets or sets a value indicating whether the container is unprivileged + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + public SwitchParameter Unprivileged { get; set; } + + /// + /// Gets or sets the container password + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + public SecureString Password { get; set; } + + /// + /// Gets or sets the container SSH public key + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + public string SSHKey { get; set; } + + /// + /// Gets or sets the container description + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + public string Description { get; set; } + + /// + /// Gets or sets a value indicating whether to start the container on boot + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + public SwitchParameter StartOnBoot { get; set; } + + /// + /// Gets or sets a value indicating whether to start the container after creation + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + public SwitchParameter Start { get; set; } + + /// + /// Processes the cmdlet + /// + protected override void ProcessRecord() + { + base.ProcessRecord(); + + try + { + Dictionary parameters; + + if (Builder != null) + { + // Use the builder parameters + parameters = Builder.Parameters; + } + else + { + // Build parameters from individual properties + parameters = new Dictionary(); + + if (!string.IsNullOrEmpty(Name)) + { + parameters["hostname"] = Name; + } + + if (!string.IsNullOrEmpty(OSTemplate)) + { + parameters["ostemplate"] = OSTemplate; + } + + if (!string.IsNullOrEmpty(Storage)) + { + parameters["storage"] = Storage; + } + + if (Memory.HasValue) + { + parameters["memory"] = Memory.Value; + } + + if (Swap.HasValue) + { + parameters["swap"] = Swap.Value; + } + + if (Cores.HasValue) + { + parameters["cores"] = Cores.Value; + } + + if (DiskSize.HasValue && !string.IsNullOrEmpty(Storage)) + { + parameters["rootfs"] = $"{Storage}:{DiskSize.Value}"; + } + + if (Unprivileged.IsPresent) + { + parameters["unprivileged"] = 1; + } + + if (Password != null) + { + parameters["password"] = ConvertSecureStringToString(Password); + } + + if (!string.IsNullOrEmpty(SSHKey)) + { + parameters["ssh-public-keys"] = SSHKey; + } + + if (!string.IsNullOrEmpty(Description)) + { + parameters["description"] = Description; + } + + if (StartOnBoot.IsPresent) + { + parameters["onboot"] = 1; + } + + if (Start.IsPresent) + { + parameters["start"] = 1; + } + } + + // Add CTID if specified + if (CTID.HasValue) + { + parameters["vmid"] = CTID.Value; + } + + // Create the container + var response = Connection.PostJson($"/nodes/{Node}/lxc", parameters); + var data = response["data"]; + var ctid = (int)data["vmid"]; + + // Wait for the task to complete + var taskId = (string)data["upid"]; + var taskStatus = WaitForTask(Node, taskId); + + if (taskStatus != "OK") + { + throw new Exception($"Failed to create container: {taskStatus}"); + } + + // Get the created container + var container = GetContainer(Node, ctid); + WriteObject(container); + } + catch (Exception ex) + { + WriteError(new ErrorRecord(ex, "NewProxmoxContainerError", ErrorCategory.OperationStopped, null)); + } + } + + private string WaitForTask(string node, string taskId) + { + var status = ""; + var attempts = 0; + var maxAttempts = 60; // Wait up to 60 seconds + + while (attempts < maxAttempts) + { + var response = Connection.GetJson($"/nodes/{node}/tasks/{taskId}/status"); + var data = response["data"]; + status = (string)data["status"]; + + if (status == "stopped") + { + return (string)data["exitstatus"]; + } + + System.Threading.Thread.Sleep(1000); + attempts++; + } + + throw new Exception("Timeout waiting for task to complete"); + } + + private ProxmoxContainer GetContainer(string node, int ctid) + { + var response = Connection.GetJson($"/nodes/{node}/lxc/{ctid}/status/current"); + var data = response["data"]; + + var container = data.ToObject(); + container.Node = node; + container.CTID = ctid; + + return container; + } + + private string ConvertSecureStringToString(SecureString secureString) + { + var ptr = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(secureString); + try + { + return System.Runtime.InteropServices.Marshal.PtrToStringBSTR(ptr); + } + finally + { + System.Runtime.InteropServices.Marshal.ZeroFreeBSTR(ptr); + } + } + } +} diff --git a/PSProxmox/Cmdlets/NewProxmoxContainerFromTurnKeyCmdlet.cs b/PSProxmox/Cmdlets/NewProxmoxContainerFromTurnKeyCmdlet.cs new file mode 100644 index 0000000..7c6fc34 --- /dev/null +++ b/PSProxmox/Cmdlets/NewProxmoxContainerFromTurnKeyCmdlet.cs @@ -0,0 +1,277 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; +using System.Security; +using PSProxmox.Models; +using PSProxmox.Session; + +namespace PSProxmox.Cmdlets +{ + /// + /// Creates a new Proxmox LXC container from a TurnKey Linux template + /// Creates a new Proxmox LXC container from a TurnKey Linux template on a Proxmox server + /// + /// + /// New-ProxmoxContainerFromTurnKey -Node "pve1" -Name "wordpress" -Template "wordpress" -Storage "local-lvm" -Memory 512 -Cores 1 -DiskSize 8 + /// Creates a new LXC container from the WordPress TurnKey Linux template on node pve1 + /// + [Cmdlet(VerbsCommon.New, "ProxmoxContainerFromTurnKey")] + [OutputType(typeof(ProxmoxContainer))] + public class NewProxmoxContainerFromTurnKeyCmdlet : ProxmoxCmdlet + { + /// + /// Gets or sets the node name + /// + [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)] + public string Node { get; set; } + + /// + /// Gets or sets the container name + /// + [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)] + public string Name { get; set; } + + /// + /// Gets or sets the TurnKey template name + /// + [Parameter(Mandatory = true, Position = 2, ValueFromPipelineByPropertyName = true)] + public string Template { get; set; } + + /// + /// Gets or sets the container ID + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + public int? CTID { get; set; } + + /// + /// Gets or sets the storage name + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + public string Storage { get; set; } = "local"; + + /// + /// Gets or sets the container memory limit in MB + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + public int Memory { get; set; } = 512; + + /// + /// Gets or sets the container swap limit in MB + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + public int Swap { get; set; } = 512; + + /// + /// Gets or sets the container CPU cores + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + public int Cores { get; set; } = 1; + + /// + /// Gets or sets the container disk size in GB + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + public int DiskSize { get; set; } = 8; + + /// + /// Gets or sets a value indicating whether the container is unprivileged + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + public SwitchParameter Unprivileged { get; set; } + + /// + /// Gets or sets the container password + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + public SecureString Password { get; set; } + + /// + /// Gets or sets the container SSH public key + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + public string SSHKey { get; set; } + + /// + /// Gets or sets a value indicating whether to start the container on boot + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + public SwitchParameter StartOnBoot { get; set; } + + /// + /// Gets or sets a value indicating whether to start the container after creation + /// + [Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true)] + public SwitchParameter Start { get; set; } + + /// + /// Processes the cmdlet + /// + protected override void ProcessRecord() + { + base.ProcessRecord(); + + try + { + // Get the template + var templatePath = GetTemplatePath(Node, Template, Storage); + if (string.IsNullOrEmpty(templatePath)) + { + // Try to download the template + WriteVerbose($"Template '{Template}' not found, attempting to download..."); + var downloadCmdlet = new SaveProxmoxTurnKeyTemplateCmdlet + { + Connection = Connection, + Node = Node, + Name = Template, + Storage = Storage + }; + + var result = InvokeCommand.InvokeScript(false, ScriptBlock.Create("param($cmdlet) & { $cmdlet.ProcessRecord(); return $cmdlet.CommandRuntime.ToString() }"), null, downloadCmdlet).FirstOrDefault(); + templatePath = result?.ToString(); + + if (string.IsNullOrEmpty(templatePath)) + { + throw new Exception($"Failed to download template '{Template}'"); + } + } + + // Create the container + var parameters = new Dictionary + { + { "hostname", Name }, + { "ostemplate", templatePath }, + { "storage", Storage }, + { "memory", Memory }, + { "swap", Swap }, + { "cores", Cores }, + { "rootfs", $"{Storage}:{DiskSize}" } + }; + + if (CTID.HasValue) + { + parameters["vmid"] = CTID.Value; + } + + if (Unprivileged.IsPresent) + { + parameters["unprivileged"] = 1; + } + + if (Password != null) + { + parameters["password"] = ConvertSecureStringToString(Password); + } + + if (!string.IsNullOrEmpty(SSHKey)) + { + parameters["ssh-public-keys"] = SSHKey; + } + + if (StartOnBoot.IsPresent) + { + parameters["onboot"] = 1; + } + + if (Start.IsPresent) + { + parameters["start"] = 1; + } + + var response = Connection.PostJson($"/nodes/{Node}/lxc", parameters); + var data = response["data"]; + var ctid = (int)data["vmid"]; + + // Wait for the task to complete + var taskId = (string)data["upid"]; + var taskStatus = WaitForTask(Node, taskId); + + if (taskStatus != "OK") + { + throw new Exception($"Failed to create container: {taskStatus}"); + } + + // Get the created container + var container = GetContainer(Node, ctid); + WriteObject(container); + } + catch (Exception ex) + { + WriteError(new ErrorRecord(ex, "NewProxmoxContainerFromTurnKeyError", ErrorCategory.OperationStopped, null)); + } + } + + private string GetTemplatePath(string node, string templateName, string storage) + { + try + { + var response = Connection.GetJson($"/nodes/{node}/storage/{storage}/content"); + var data = response["data"]; + + foreach (var item in data) + { + if (item["content"] != null && item["content"].ToString() == "vztmpl" && + item["volid"] != null && item["volid"].ToString().Contains(templateName)) + { + return item["volid"].ToString(); + } + } + } + catch (Exception) + { + // Ignore errors and return null + } + + return null; + } + + private string WaitForTask(string node, string taskId) + { + var status = ""; + var attempts = 0; + var maxAttempts = 60; // Wait up to 60 seconds + + while (attempts < maxAttempts) + { + var response = Connection.GetJson($"/nodes/{node}/tasks/{taskId}/status"); + var data = response["data"]; + status = (string)data["status"]; + + if (status == "stopped") + { + return (string)data["exitstatus"]; + } + + System.Threading.Thread.Sleep(1000); + attempts++; + } + + throw new Exception("Timeout waiting for task to complete"); + } + + private ProxmoxContainer GetContainer(string node, int ctid) + { + var response = Connection.GetJson($"/nodes/{node}/lxc/{ctid}/status/current"); + var data = response["data"]; + + var container = data.ToObject(); + container.Node = node; + container.CTID = ctid; + + return container; + } + + private string ConvertSecureStringToString(SecureString secureString) + { + var ptr = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(secureString); + try + { + return System.Runtime.InteropServices.Marshal.PtrToStringBSTR(ptr); + } + finally + { + System.Runtime.InteropServices.Marshal.ZeroFreeBSTR(ptr); + } + } + } +} diff --git a/PSProxmox/Cmdlets/RemoveProxmoxContainerCmdlet.cs b/PSProxmox/Cmdlets/RemoveProxmoxContainerCmdlet.cs new file mode 100644 index 0000000..e04c553 --- /dev/null +++ b/PSProxmox/Cmdlets/RemoveProxmoxContainerCmdlet.cs @@ -0,0 +1,101 @@ +using System; +using System.Management.Automation; +using PSProxmox.Session; + +namespace PSProxmox.Cmdlets +{ + /// + /// Removes a Proxmox LXC container + /// Removes a Proxmox LXC container from a Proxmox server + /// + /// + /// Remove-ProxmoxContainer -Node "pve1" -CTID 100 + /// Removes the container with CTID 100 from node pve1 + /// + /// + /// Remove-ProxmoxContainer -Node "pve1" -CTID 100 -Confirm:$false + /// Removes the container with CTID 100 from node pve1 without confirmation + /// + [Cmdlet(VerbsCommon.Remove, "ProxmoxContainer", SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.High)] + public class RemoveProxmoxContainerCmdlet : ProxmoxCmdlet + { + /// + /// Gets or sets the node name + /// + [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)] + public string Node { get; set; } + + /// + /// Gets or sets the container ID + /// + [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)] + public int CTID { get; set; } + + /// + /// Gets or sets a value indicating whether to force removal + /// + [Parameter(Mandatory = false)] + public SwitchParameter Force { get; set; } + + /// + /// Processes the cmdlet + /// + protected override void ProcessRecord() + { + base.ProcessRecord(); + + try + { + if (ShouldProcess($"Container {CTID} on node {Node}", "Remove")) + { + var parameters = new System.Collections.Generic.Dictionary(); + + if (Force.IsPresent) + { + parameters["force"] = 1; + } + + var response = Connection.DeleteJson($"/nodes/{Node}/lxc/{CTID}", parameters); + var data = response["data"]; + var taskId = (string)data["upid"]; + + var taskStatus = WaitForTask(Node, taskId); + if (taskStatus != "OK") + { + throw new Exception($"Failed to remove container: {taskStatus}"); + } + + WriteVerbose($"Container {CTID} on node {Node} removed successfully"); + } + } + catch (Exception ex) + { + WriteError(new ErrorRecord(ex, "RemoveProxmoxContainerError", ErrorCategory.OperationStopped, null)); + } + } + + private string WaitForTask(string node, string taskId) + { + var status = ""; + var attempts = 0; + var maxAttempts = 60; // Wait up to 60 seconds + + while (attempts < maxAttempts) + { + var response = Connection.GetJson($"/nodes/{node}/tasks/{taskId}/status"); + var data = response["data"]; + status = (string)data["status"]; + + if (status == "stopped") + { + return (string)data["exitstatus"]; + } + + System.Threading.Thread.Sleep(1000); + attempts++; + } + + throw new Exception("Timeout waiting for task to complete"); + } + } +} diff --git a/PSProxmox/Cmdlets/RestartProxmoxContainerCmdlet.cs b/PSProxmox/Cmdlets/RestartProxmoxContainerCmdlet.cs new file mode 100644 index 0000000..0958431 --- /dev/null +++ b/PSProxmox/Cmdlets/RestartProxmoxContainerCmdlet.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Management.Automation; +using PSProxmox.Models; +using PSProxmox.Session; + +namespace PSProxmox.Cmdlets +{ + /// + /// Restarts a Proxmox LXC container + /// Restarts a Proxmox LXC container on a Proxmox server + /// + /// + /// Restart-ProxmoxContainer -Node "pve1" -CTID 100 + /// Restarts the container with CTID 100 on node pve1 + /// + [Cmdlet(VerbsLifecycle.Restart, "ProxmoxContainer")] + [OutputType(typeof(ProxmoxContainer))] + public class RestartProxmoxContainerCmdlet : ProxmoxCmdlet + { + /// + /// Gets or sets the node name + /// + [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)] + public string Node { get; set; } + + /// + /// Gets or sets the container ID + /// + [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)] + public int CTID { get; set; } + + /// + /// Gets or sets a value indicating whether to wait for the container to restart + /// + [Parameter(Mandatory = false)] + public SwitchParameter Wait { get; set; } + + /// + /// Gets or sets the timeout in seconds + /// + [Parameter(Mandatory = false)] + public int Timeout { get; set; } = 60; + + /// + /// Processes the cmdlet + /// + protected override void ProcessRecord() + { + base.ProcessRecord(); + + try + { + var parameters = new Dictionary(); + + if (Timeout > 0) + { + parameters["timeout"] = Timeout; + } + + var response = Connection.PostJson($"/nodes/{Node}/lxc/{CTID}/status/restart", parameters); + var data = response["data"]; + var taskId = (string)data["upid"]; + + if (Wait.IsPresent) + { + var taskStatus = WaitForTask(Node, taskId); + if (taskStatus != "OK") + { + throw new Exception($"Failed to restart container: {taskStatus}"); + } + + // Get the container status + var container = GetContainer(Node, CTID); + WriteObject(container); + } + else + { + WriteVerbose($"Container {CTID} on node {Node} restart initiated"); + } + } + catch (Exception ex) + { + WriteError(new ErrorRecord(ex, "RestartProxmoxContainerError", ErrorCategory.OperationStopped, null)); + } + } + + private string WaitForTask(string node, string taskId) + { + var status = ""; + var attempts = 0; + var maxAttempts = Timeout > 0 ? Timeout : 60; + + while (attempts < maxAttempts) + { + var response = Connection.GetJson($"/nodes/{node}/tasks/{taskId}/status"); + var data = response["data"]; + status = (string)data["status"]; + + if (status == "stopped") + { + return (string)data["exitstatus"]; + } + + System.Threading.Thread.Sleep(1000); + attempts++; + } + + throw new Exception("Timeout waiting for task to complete"); + } + + private ProxmoxContainer GetContainer(string node, int ctid) + { + var response = Connection.GetJson($"/nodes/{node}/lxc/{ctid}/status/current"); + var data = response["data"]; + + var container = data.ToObject(); + container.Node = node; + container.CTID = ctid; + + return container; + } + } +} diff --git a/PSProxmox/Cmdlets/SaveProxmoxTurnKeyTemplateCmdlet.cs b/PSProxmox/Cmdlets/SaveProxmoxTurnKeyTemplateCmdlet.cs new file mode 100644 index 0000000..e98bda9 --- /dev/null +++ b/PSProxmox/Cmdlets/SaveProxmoxTurnKeyTemplateCmdlet.cs @@ -0,0 +1,206 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; +using PSProxmox.Models; +using PSProxmox.Session; + +namespace PSProxmox.Cmdlets +{ + /// + /// Downloads a TurnKey Linux template to a Proxmox server + /// Downloads a TurnKey Linux template to a Proxmox server for use with LXC containers + /// + /// + /// Save-ProxmoxTurnKeyTemplate -Node "pve1" -Name "wordpress" -Storage "local" + /// Downloads the WordPress TurnKey Linux template to the local storage on node pve1 + /// + [Cmdlet(VerbsData.Save, "ProxmoxTurnKeyTemplate")] + [OutputType(typeof(string))] + public class SaveProxmoxTurnKeyTemplateCmdlet : ProxmoxCmdlet + { + /// + /// Gets or sets the node name + /// + [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)] + public string Node { get; set; } + + /// + /// Gets or sets the template name + /// + [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)] + public string Name { get; set; } + + /// + /// Gets or sets the storage name + /// + [Parameter(Mandatory = true, Position = 2, ValueFromPipelineByPropertyName = true)] + public string Storage { get; set; } + + /// + /// Gets or sets a value indicating whether to force download even if the template already exists + /// + [Parameter(Mandatory = false)] + public SwitchParameter Force { get; set; } + + /// + /// Processes the cmdlet + /// + protected override void ProcessRecord() + { + base.ProcessRecord(); + + try + { + // Get the template information + var templates = GetTemplates(Node); + var template = templates.FirstOrDefault(t => t.Name.Equals(Name, StringComparison.OrdinalIgnoreCase)); + + if (template == null) + { + throw new Exception($"Template '{Name}' not found"); + } + + // Check if the template is already downloaded + if (!Force.IsPresent) + { + var existingTemplates = GetDownloadedTemplates(Node, Storage); + var existingTemplate = existingTemplates.FirstOrDefault(t => t.Contains(template.Name)); + + if (existingTemplate != null) + { + WriteVerbose($"Template '{Name}' already exists at '{existingTemplate}'"); + WriteObject(existingTemplate); + return; + } + } + + // Download the template + var parameters = new Dictionary + { + { "template", template.URL }, + { "storage", Storage } + }; + + var response = Connection.PostJson($"/nodes/{Node}/aplinfo", parameters); + var data = response["data"]; + var taskId = (string)data["upid"]; + + var taskStatus = WaitForTask(Node, taskId); + if (taskStatus != "OK") + { + throw new Exception($"Failed to download template: {taskStatus}"); + } + + // Get the downloaded template path + var downloadedTemplates = GetDownloadedTemplates(Node, Storage); + var downloadedTemplate = downloadedTemplates.FirstOrDefault(t => t.Contains(template.Name)); + + if (downloadedTemplate == null) + { + throw new Exception($"Failed to find downloaded template '{Name}'"); + } + + WriteVerbose($"Template '{Name}' downloaded to '{downloadedTemplate}'"); + WriteObject(downloadedTemplate); + } + catch (Exception ex) + { + WriteError(new ErrorRecord(ex, "SaveProxmoxTurnKeyTemplateError", ErrorCategory.OperationStopped, null)); + } + } + + private List GetTemplates(string node) + { + var templates = new List(); + + try + { + // Get available templates from the appliance info + var response = Connection.GetJson($"/nodes/{node}/aplinfo"); + var data = response["data"]; + + foreach (var item in data) + { + // Only include TurnKey templates + if (item["package"] != null && item["package"].ToString().Contains("turnkey")) + { + var template = item.ToObject(); + templates.Add(template); + } + } + } + catch (Exception ex) + { + throw new Exception($"Failed to get templates: {ex.Message}"); + } + + return templates; + } + + private List GetDownloadedTemplates(string node, string storage) + { + var templates = new List(); + + try + { + var response = Connection.GetJson($"/nodes/{node}/storage/{storage}/content"); + var data = response["data"]; + + foreach (var item in data) + { + if (item["content"] != null && item["content"].ToString() == "vztmpl" && + item["volid"] != null && item["volid"].ToString().Contains("turnkey")) + { + templates.Add(item["volid"].ToString()); + } + } + } + catch (Exception ex) + { + throw new Exception($"Failed to get downloaded templates: {ex.Message}"); + } + + return templates; + } + + private string WaitForTask(string node, string taskId) + { + var status = ""; + var attempts = 0; + var maxAttempts = 300; // Wait up to 5 minutes + + while (attempts < maxAttempts) + { + var response = Connection.GetJson($"/nodes/{node}/tasks/{taskId}/status"); + var data = response["data"]; + status = (string)data["status"]; + + if (status == "stopped") + { + return (string)data["exitstatus"]; + } + + // Show progress + if (data["pid"] != null) + { + var progressResponse = Connection.GetJson($"/nodes/{node}/tasks/{taskId}/log?start=0"); + var progressData = progressResponse["data"]; + + foreach (var item in progressData) + { + if (item["t"] != null && item["t"].ToString() == "TASK_STATUS") + { + WriteProgress(new ProgressRecord(1, "Downloading template", item["t"].ToString())); + } + } + } + + System.Threading.Thread.Sleep(1000); + attempts++; + } + + throw new Exception("Timeout waiting for task to complete"); + } + } +} diff --git a/PSProxmox/Cmdlets/StartProxmoxContainerCmdlet.cs b/PSProxmox/Cmdlets/StartProxmoxContainerCmdlet.cs new file mode 100644 index 0000000..9f55195 --- /dev/null +++ b/PSProxmox/Cmdlets/StartProxmoxContainerCmdlet.cs @@ -0,0 +1,110 @@ +using System; +using System.Management.Automation; +using PSProxmox.Models; +using PSProxmox.Session; + +namespace PSProxmox.Cmdlets +{ + /// + /// Starts a Proxmox LXC container + /// Starts a Proxmox LXC container on a Proxmox server + /// + /// + /// Start-ProxmoxContainer -Node "pve1" -CTID 100 + /// Starts the container with CTID 100 on node pve1 + /// + [Cmdlet(VerbsLifecycle.Start, "ProxmoxContainer")] + [OutputType(typeof(ProxmoxContainer))] + public class StartProxmoxContainerCmdlet : ProxmoxCmdlet + { + /// + /// Gets or sets the node name + /// + [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)] + public string Node { get; set; } + + /// + /// Gets or sets the container ID + /// + [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)] + public int CTID { get; set; } + + /// + /// Gets or sets a value indicating whether to wait for the container to start + /// + [Parameter(Mandatory = false)] + public SwitchParameter Wait { get; set; } + + /// + /// Processes the cmdlet + /// + protected override void ProcessRecord() + { + base.ProcessRecord(); + + try + { + var response = Connection.PostJson($"/nodes/{Node}/lxc/{CTID}/status/start", null); + var data = response["data"]; + var taskId = (string)data["upid"]; + + if (Wait.IsPresent) + { + var taskStatus = WaitForTask(Node, taskId); + if (taskStatus != "OK") + { + throw new Exception($"Failed to start container: {taskStatus}"); + } + + // Get the container status + var container = GetContainer(Node, CTID); + WriteObject(container); + } + else + { + WriteVerbose($"Container {CTID} on node {Node} start initiated"); + } + } + catch (Exception ex) + { + WriteError(new ErrorRecord(ex, "StartProxmoxContainerError", ErrorCategory.OperationStopped, null)); + } + } + + private string WaitForTask(string node, string taskId) + { + var status = ""; + var attempts = 0; + var maxAttempts = 60; // Wait up to 60 seconds + + while (attempts < maxAttempts) + { + var response = Connection.GetJson($"/nodes/{node}/tasks/{taskId}/status"); + var data = response["data"]; + status = (string)data["status"]; + + if (status == "stopped") + { + return (string)data["exitstatus"]; + } + + System.Threading.Thread.Sleep(1000); + attempts++; + } + + throw new Exception("Timeout waiting for task to complete"); + } + + private ProxmoxContainer GetContainer(string node, int ctid) + { + var response = Connection.GetJson($"/nodes/{node}/lxc/{ctid}/status/current"); + var data = response["data"]; + + var container = data.ToObject(); + container.Node = node; + container.CTID = ctid; + + return container; + } + } +} diff --git a/PSProxmox/Cmdlets/StopProxmoxContainerCmdlet.cs b/PSProxmox/Cmdlets/StopProxmoxContainerCmdlet.cs new file mode 100644 index 0000000..45a5414 --- /dev/null +++ b/PSProxmox/Cmdlets/StopProxmoxContainerCmdlet.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Management.Automation; +using PSProxmox.Models; +using PSProxmox.Session; + +namespace PSProxmox.Cmdlets +{ + /// + /// Stops a Proxmox LXC container + /// Stops a Proxmox LXC container on a Proxmox server + /// + /// + /// Stop-ProxmoxContainer -Node "pve1" -CTID 100 + /// Stops the container with CTID 100 on node pve1 + /// + /// + /// Stop-ProxmoxContainer -Node "pve1" -CTID 100 -Force + /// Forces the container with CTID 100 on node pve1 to stop + /// + [Cmdlet(VerbsLifecycle.Stop, "ProxmoxContainer")] + [OutputType(typeof(ProxmoxContainer))] + public class StopProxmoxContainerCmdlet : ProxmoxCmdlet + { + /// + /// Gets or sets the node name + /// + [Parameter(Mandatory = true, Position = 0, ValueFromPipelineByPropertyName = true)] + public string Node { get; set; } + + /// + /// Gets or sets the container ID + /// + [Parameter(Mandatory = true, Position = 1, ValueFromPipelineByPropertyName = true)] + public int CTID { get; set; } + + /// + /// Gets or sets a value indicating whether to force stop the container + /// + [Parameter(Mandatory = false)] + public SwitchParameter Force { get; set; } + + /// + /// Gets or sets a value indicating whether to wait for the container to stop + /// + [Parameter(Mandatory = false)] + public SwitchParameter Wait { get; set; } + + /// + /// Gets or sets the timeout in seconds + /// + [Parameter(Mandatory = false)] + public int Timeout { get; set; } = 60; + + /// + /// Processes the cmdlet + /// + protected override void ProcessRecord() + { + base.ProcessRecord(); + + try + { + var parameters = new Dictionary(); + + if (Force.IsPresent) + { + parameters["force"] = 1; + } + + if (Timeout > 0) + { + parameters["timeout"] = Timeout; + } + + var response = Connection.PostJson($"/nodes/{Node}/lxc/{CTID}/status/stop", parameters); + var data = response["data"]; + var taskId = (string)data["upid"]; + + if (Wait.IsPresent) + { + var taskStatus = WaitForTask(Node, taskId); + if (taskStatus != "OK") + { + throw new Exception($"Failed to stop container: {taskStatus}"); + } + + // Get the container status + var container = GetContainer(Node, CTID); + WriteObject(container); + } + else + { + WriteVerbose($"Container {CTID} on node {Node} stop initiated"); + } + } + catch (Exception ex) + { + WriteError(new ErrorRecord(ex, "StopProxmoxContainerError", ErrorCategory.OperationStopped, null)); + } + } + + private string WaitForTask(string node, string taskId) + { + var status = ""; + var attempts = 0; + var maxAttempts = Timeout > 0 ? Timeout : 60; + + while (attempts < maxAttempts) + { + var response = Connection.GetJson($"/nodes/{node}/tasks/{taskId}/status"); + var data = response["data"]; + status = (string)data["status"]; + + if (status == "stopped") + { + return (string)data["exitstatus"]; + } + + System.Threading.Thread.Sleep(1000); + attempts++; + } + + throw new Exception("Timeout waiting for task to complete"); + } + + private ProxmoxContainer GetContainer(string node, int ctid) + { + var response = Connection.GetJson($"/nodes/{node}/lxc/{ctid}/status/current"); + var data = response["data"]; + + var container = data.ToObject(); + container.Node = node; + container.CTID = ctid; + + return container; + } + } +} diff --git a/PSProxmox/Models/ProxmoxContainer.cs b/PSProxmox/Models/ProxmoxContainer.cs new file mode 100644 index 0000000..b764324 --- /dev/null +++ b/PSProxmox/Models/ProxmoxContainer.cs @@ -0,0 +1,229 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace PSProxmox.Models +{ + /// + /// Represents a Proxmox LXC container + /// + public class ProxmoxContainer + { + /// + /// Gets or sets the container ID + /// + [JsonProperty("vmid")] + public int CTID { get; set; } + + /// + /// Gets or sets the container name + /// + [JsonProperty("name")] + public string Name { get; set; } + + /// + /// Gets or sets the node name + /// + [JsonProperty("node")] + public string Node { get; set; } + + /// + /// Gets or sets the container status + /// + [JsonProperty("status")] + public string Status { get; set; } + + /// + /// Gets or sets the container uptime in seconds + /// + [JsonProperty("uptime")] + public long Uptime { get; set; } + + /// + /// Gets or sets the container CPU usage + /// + [JsonProperty("cpu")] + public double CPU { get; set; } + + /// + /// Gets or sets the container memory usage in bytes + /// + [JsonProperty("mem")] + public long Memory { get; set; } + + /// + /// Gets or sets the container maximum memory in bytes + /// + [JsonProperty("maxmem")] + public long MaxMemory { get; set; } + + /// + /// Gets or sets the container disk usage in bytes + /// + [JsonProperty("disk")] + public long Disk { get; set; } + + /// + /// Gets or sets the container maximum disk size in bytes + /// + [JsonProperty("maxdisk")] + public long MaxDisk { get; set; } + + /// + /// Gets or sets the container swap usage in bytes + /// + [JsonProperty("swap")] + public long Swap { get; set; } + + /// + /// Gets or sets the container maximum swap in bytes + /// + [JsonProperty("maxswap")] + public long MaxSwap { get; set; } + + /// + /// Gets or sets the container network receive rate in bytes per second + /// + [JsonProperty("netin")] + public long NetworkIn { get; set; } + + /// + /// Gets or sets the container network transmit rate in bytes per second + /// + [JsonProperty("netout")] + public long NetworkOut { get; set; } + + /// + /// Gets or sets the container disk read rate in bytes per second + /// + [JsonProperty("diskread")] + public long DiskRead { get; set; } + + /// + /// Gets or sets the container disk write rate in bytes per second + /// + [JsonProperty("diskwrite")] + public long DiskWrite { get; set; } + + /// + /// Gets or sets the container template flag + /// + [JsonProperty("template")] + public int Template { get; set; } + + /// + /// Gets or sets the container description + /// + [JsonProperty("description")] + public string Description { get; set; } + + /// + /// Gets or sets the container OS type + /// + [JsonProperty("ostype")] + public string OSType { get; set; } + + /// + /// Gets or sets the container IP address + /// + [JsonProperty("ip")] + public string IPAddress { get; set; } + + /// + /// Gets or sets the container hostname + /// + [JsonProperty("hostname")] + public string Hostname { get; set; } + + /// + /// Gets or sets the container creation time + /// + [JsonProperty("creation")] + public long CreationTime { get; set; } + + /// + /// Gets or sets the container tags + /// + [JsonProperty("tags")] + public string Tags { get; set; } + + /// + /// Gets or sets the container lock + /// + [JsonProperty("lock")] + public string Lock { get; set; } + + /// + /// Gets or sets the container unprivileged flag + /// + [JsonProperty("unprivileged")] + public int Unprivileged { get; set; } + + /// + /// Gets or sets the container features + /// + [JsonProperty("features")] + public string Features { get; set; } + + /// + /// Gets or sets the container configuration + /// + [JsonProperty("config")] + public Dictionary Config { get; set; } + + /// + /// Gets the container creation date + /// + public DateTime CreationDate + { + get + { + return DateTimeOffset.FromUnixTimeSeconds(CreationTime).DateTime; + } + } + + /// + /// Gets the container uptime as a TimeSpan + /// + public TimeSpan UptimeSpan + { + get + { + return TimeSpan.FromSeconds(Uptime); + } + } + + /// + /// Gets a value indicating whether the container is running + /// + public bool IsRunning + { + get + { + return Status == "running"; + } + } + + /// + /// Gets a value indicating whether the container is a template + /// + public bool IsTemplate + { + get + { + return Template == 1; + } + } + + /// + /// Gets a value indicating whether the container is unprivileged + /// + public bool IsUnprivileged + { + get + { + return Unprivileged == 1; + } + } + } +} diff --git a/PSProxmox/Models/ProxmoxContainerBuilder.cs b/PSProxmox/Models/ProxmoxContainerBuilder.cs new file mode 100644 index 0000000..2c25cfe --- /dev/null +++ b/PSProxmox/Models/ProxmoxContainerBuilder.cs @@ -0,0 +1,187 @@ +using System.Collections.Generic; + +namespace PSProxmox.Models +{ + /// + /// Builder class for creating Proxmox LXC containers + /// + public class ProxmoxContainerBuilder + { + private readonly Dictionary _parameters; + + /// + /// Initializes a new instance of the class + /// + /// The container name + public ProxmoxContainerBuilder(string name) + { + _parameters = new Dictionary + { + { "hostname", name } + }; + } + + /// + /// Gets the parameters for creating the container + /// + public Dictionary Parameters => _parameters; + + /// + /// Sets the container OS template + /// + /// The OS template + /// The builder instance + public ProxmoxContainerBuilder WithOSTemplate(string ostemplate) + { + _parameters["ostemplate"] = ostemplate; + return this; + } + + /// + /// Sets the container storage + /// + /// The storage name + /// The builder instance + public ProxmoxContainerBuilder WithStorage(string storage) + { + _parameters["storage"] = storage; + return this; + } + + /// + /// Sets the container memory limit + /// + /// The memory limit in MB + /// The builder instance + public ProxmoxContainerBuilder WithMemory(int memory) + { + _parameters["memory"] = memory; + return this; + } + + /// + /// Sets the container swap limit + /// + /// The swap limit in MB + /// The builder instance + public ProxmoxContainerBuilder WithSwap(int swap) + { + _parameters["swap"] = swap; + return this; + } + + /// + /// Sets the container CPU cores + /// + /// The number of CPU cores + /// The builder instance + public ProxmoxContainerBuilder WithCores(int cores) + { + _parameters["cores"] = cores; + return this; + } + + /// + /// Sets the container disk size + /// + /// The disk size in GB + /// The builder instance + public ProxmoxContainerBuilder WithDiskSize(int diskSize) + { + _parameters["rootfs"] = $"local-lvm:{diskSize}"; + return this; + } + + /// + /// Sets the container disk size with specific storage + /// + /// The storage name + /// The disk size in GB + /// The builder instance + public ProxmoxContainerBuilder WithDiskSize(string storage, int diskSize) + { + _parameters["rootfs"] = $"{storage}:{diskSize}"; + return this; + } + + /// + /// Sets the container to be unprivileged + /// + /// Whether the container is unprivileged + /// The builder instance + public ProxmoxContainerBuilder WithUnprivileged(bool unprivileged) + { + _parameters["unprivileged"] = unprivileged ? 1 : 0; + return this; + } + + /// + /// Sets the container password + /// + /// The container password + /// The builder instance + public ProxmoxContainerBuilder WithPassword(string password) + { + _parameters["password"] = password; + return this; + } + + /// + /// Sets the container SSH public key + /// + /// The SSH public key + /// The builder instance + public ProxmoxContainerBuilder WithSSHKey(string sshKey) + { + _parameters["ssh-public-keys"] = sshKey; + return this; + } + + /// + /// Sets the container network interface + /// + /// The interface name + /// The bridge name + /// The IP address + /// The gateway address + /// The builder instance + public ProxmoxContainerBuilder WithNetwork(string name, string bridge, string ip, string gateway) + { + _parameters[$"net{name}"] = $"name=eth0,bridge={bridge},ip={ip},gw={gateway}"; + return this; + } + + /// + /// Sets the container description + /// + /// The container description + /// The builder instance + public ProxmoxContainerBuilder WithDescription(string description) + { + _parameters["description"] = description; + return this; + } + + /// + /// Sets the container start on boot flag + /// + /// Whether to start the container on boot + /// The builder instance + public ProxmoxContainerBuilder WithStartOnBoot(bool start) + { + _parameters["onboot"] = start ? 1 : 0; + return this; + } + + /// + /// Sets the container start after creation flag + /// + /// Whether to start the container after creation + /// The builder instance + public ProxmoxContainerBuilder WithStart(bool start) + { + _parameters["start"] = start ? 1 : 0; + return this; + } + } +} diff --git a/PSProxmox/Models/ProxmoxTurnKeyTemplate.cs b/PSProxmox/Models/ProxmoxTurnKeyTemplate.cs new file mode 100644 index 0000000..18aeca0 --- /dev/null +++ b/PSProxmox/Models/ProxmoxTurnKeyTemplate.cs @@ -0,0 +1,137 @@ +using System; +using Newtonsoft.Json; + +namespace PSProxmox.Models +{ + /// + /// Represents a TurnKey Linux template for Proxmox LXC containers + /// + public class ProxmoxTurnKeyTemplate + { + /// + /// Gets or sets the template name + /// + [JsonProperty("template")] + public string Name { get; set; } + + /// + /// Gets or sets the template title + /// + [JsonProperty("title")] + public string Title { get; set; } + + /// + /// Gets or sets the template description + /// + [JsonProperty("description")] + public string Description { get; set; } + + /// + /// Gets or sets the template section + /// + [JsonProperty("section")] + public string Section { get; set; } + + /// + /// Gets or sets the template OS + /// + [JsonProperty("os")] + public string OS { get; set; } + + /// + /// Gets or sets the template version + /// + [JsonProperty("version")] + public string Version { get; set; } + + /// + /// Gets or sets the template package + /// + [JsonProperty("package")] + public string Package { get; set; } + + /// + /// Gets or sets the template source + /// + [JsonProperty("source")] + public string Source { get; set; } + + /// + /// Gets or sets the template URL + /// + [JsonProperty("url")] + public string URL { get; set; } + + /// + /// Gets or sets the template size in bytes + /// + [JsonProperty("size")] + public long Size { get; set; } + + /// + /// Gets or sets the template MD5 checksum + /// + [JsonProperty("md5sum")] + public string MD5Sum { get; set; } + + /// + /// Gets or sets the template SHA1 checksum + /// + [JsonProperty("sha1sum")] + public string SHA1Sum { get; set; } + + /// + /// Gets or sets the template SHA256 checksum + /// + [JsonProperty("sha256sum")] + public string SHA256Sum { get; set; } + + /// + /// Gets or sets the template architecture + /// + [JsonProperty("architecture")] + public string Architecture { get; set; } + + /// + /// Gets or sets the template infopage URL + /// + [JsonProperty("infopage")] + public string InfoPage { get; set; } + + /// + /// Gets or sets the template download URL + /// + [JsonProperty("location")] + public string Location { get; set; } + + /// + /// Gets the template size in a human-readable format + /// + public string HumanSize + { + get + { + string[] sizes = { "B", "KB", "MB", "GB", "TB" }; + double len = Size; + int order = 0; + while (len >= 1024 && order < sizes.Length - 1) + { + order++; + len = len / 1024; + } + return $"{len:0.##} {sizes[order]}"; + } + } + + /// + /// Gets the full template name including version + /// + public string FullName + { + get + { + return $"{Name}-{Version}"; + } + } + } +} diff --git a/README.md b/README.md index 00309ac..89e2336 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,12 @@ PSProxmox is a C#-based PowerShell module for managing Proxmox VE clusters. It p ## Features - **Session Management**: Authenticate and persist sessions with Proxmox VE clusters -- **Core CRUD Operations**: Manage VMs, Storage, Network, Users, Roles, SDN, and Clusters +- **Core CRUD Operations**: Manage VMs, LXC Containers, Storage, Network, Users, Roles, SDN, and Clusters - **Templates**: Create and use VM deployment templates - **Cloud Images**: Download, customize, and create templates from cloud images - **Cloud-Init Support**: Configure VMs with Cloud-Init for easy customization +- **LXC Containers**: Create and manage Linux Containers with full CRUD operations +- **TurnKey Templates**: Download and use TurnKey Linux templates for LXC containers - **Mass Creation**: Bulk create VMs with prefix/counter - **IP Management**: CIDR parsing, subnetting, FIFO IP queue assignment - **Structured Objects**: All outputs are typed C# classes (PowerShell-native) @@ -249,6 +251,59 @@ $pool = Get-ProxmoxIPPool -Name "Production" Clear-ProxmoxIPPool -Name "Production" ``` +### Managing LXC Containers + +```powershell +# Get all LXC containers +$containers = Get-ProxmoxContainer + +# Get a specific container +$container = Get-ProxmoxContainer -CTID 100 + +# Get containers by name pattern (supports wildcards and regex) +$webContainers = Get-ProxmoxContainer -Name "web*" +$dbContainers = Get-ProxmoxContainer -Name "^db\d+$" + +# Create a new container +$container = New-ProxmoxContainer -Node "pve1" -Name "web-container" -OSTemplate "local:vztmpl/ubuntu-20.04-standard_20.04-1_amd64.tar.gz" -Storage "local-lvm" -Memory 512 -Cores 1 -DiskSize 8 -Unprivileged -Start + +# Create a container using a builder +$builder = New-ProxmoxContainerBuilder -Name "db-container" +$builder.WithOSTemplate("local:vztmpl/ubuntu-20.04-standard_20.04-1_amd64.tar.gz") + .WithStorage("local-lvm") + .WithMemory(1024) + .WithCores(2) + .WithDiskSize(16) + .WithUnprivileged($true) + .WithStart($true) + +$container = New-ProxmoxContainer -Node "pve1" -Builder $builder + +# Start, stop, and restart containers +Start-ProxmoxContainer -Node "pve1" -CTID 100 -Wait +Stop-ProxmoxContainer -Node "pve1" -CTID 100 -Wait +Restart-ProxmoxContainer -Node "pve1" -CTID 100 -Wait + +# Remove a container +Remove-ProxmoxContainer -Node "pve1" -CTID 100 -Confirm:$false +``` + +### Working with TurnKey Linux Templates + +```powershell +# List available TurnKey templates +$templates = Get-ProxmoxTurnKeyTemplate + +# Get TurnKey templates by name pattern +$wordpressTemplates = Get-ProxmoxTurnKeyTemplate -Name "wordpress*" + +# Download a TurnKey template +$templatePath = Save-ProxmoxTurnKeyTemplate -Node "pve1" -Name "wordpress" -Storage "local" + +# Create a container from a TurnKey template +$container = New-ProxmoxContainerFromTurnKey -Node "pve1" -Name "wordpress" -Template "wordpress" -Storage "local-lvm" -Memory 512 -Cores 1 -DiskSize 8 -Start +``` + ## Disconnecting ```powershell @@ -290,6 +345,7 @@ For detailed documentation, see the [Documentation](Documentation) directory. Th - [Guides](Documentation/guides/README.md) - [Installation Guide](Documentation/guides/Installation.md) - [Cloud Image Templates Guide](Documentation/guides/CloudImageTemplates.md) + - [LXC Containers Guide](Documentation/guides/LXCContainers.md) ## Contributing