1
0
mirror of https://github.com/lxc/incus.git synced 2026-02-05 09:46:19 +01:00

Attempt to make the Incus Agent on Windows better integrated.

Signed-off-by: Marc Olivier Bergeron <mbergeron28@proton.me>
This commit is contained in:
Marc Olivier Bergeron
2025-12-10 23:28:19 -05:00
parent be729e95f1
commit 656392fa5d
11 changed files with 100 additions and 102 deletions

View File

@@ -7,6 +7,7 @@ import (
"io"
"net/http"
"os"
"path/filepath"
incus "github.com/lxc/incus/v6/client"
"github.com/lxc/incus/v6/internal/ports"
@@ -82,7 +83,7 @@ func api10Put(d *Daemon, r *http.Request) response.Response {
}
// Try connecting to the host.
client, err := getClient(d.serverCID, int(d.serverPort), d.serverCertificate)
client, err := getClient(d.serverCID, int(d.serverPort), d.serverCertificate, d.secretsLocation)
if err != nil {
return response.ErrorResponse(http.StatusInternalServerError, err.Error())
}
@@ -167,13 +168,13 @@ func stopDevIncusServer(d *Daemon) error {
return nil
}
func getClient(CID uint32, port int, serverCertificate string) (*http.Client, error) {
agentCert, err := os.ReadFile("agent.crt")
func getClient(CID uint32, port int, serverCertificate string, secretsLocation string) (*http.Client, error) {
agentCert, err := os.ReadFile(filepath.Join(secretsLocation, "agent.crt"))
if err != nil {
return nil, err
}
agentKey, err := os.ReadFile("agent.key")
agentKey, err := os.ReadFile(filepath.Join(secretsLocation, "agent.key"))
if err != nil {
return nil, err
}
@@ -193,12 +194,12 @@ func startHTTPServer(d *Daemon, debug bool) error {
}
// Load the expected server certificate.
cert, err := localtls.ReadCert("server.crt")
cert, err := localtls.ReadCert(filepath.Join(d.secretsLocation, "server.crt"))
if err != nil {
return fmt.Errorf("Failed to read client certificate: %w", err)
}
tlsConfig, err := serverTLSConfig()
tlsConfig, err := serverTLSConfig(d.secretsLocation)
if err != nil {
return fmt.Errorf("Failed to get TLS config: %w", err)
}

View File

@@ -11,6 +11,8 @@ type Daemon struct {
// Event servers
events *events.Server
secretsLocation string
// ContextID and port of the host socket server.
serverCID uint32
serverPort uint32
@@ -25,11 +27,12 @@ type Daemon struct {
}
// newDaemon returns a new Daemon object with the given configuration.
func newDaemon(debug, verbose bool) *Daemon {
func newDaemon(debug, verbose bool, secretsLocation string) *Daemon {
hostEvents := events.NewServer(debug, verbose, nil)
return &Daemon{
events: hostEvents,
chConnected: make(chan struct{}),
secretsLocation: secretsLocation,
events: hostEvents,
chConnected: make(chan struct{}),
}
}

View File

@@ -40,7 +40,7 @@ type devIncusHandler struct {
func getVsockClient(d *Daemon) (incus.InstanceServer, error) {
// Try connecting to the host.
client, err := getClient(d.serverCID, int(d.serverPort), d.serverCertificate)
client, err := getClient(d.serverCID, int(d.serverPort), d.serverCertificate, d.secretsLocation)
if err != nil {
return nil, err
}

View File

@@ -10,9 +10,10 @@ import (
)
type cmdGlobal struct {
flagVersion bool
flagHelp bool
flagService bool
flagVersion bool
flagHelp bool
flagService bool
flagSecretsLocation string
flagLogVerbose bool
flagLogDebug bool
@@ -35,6 +36,7 @@ func main() {
app.PersistentFlags().BoolVarP(&globalCmd.flagHelp, "help", "h", false, "Print help")
app.PersistentFlags().BoolVarP(&globalCmd.flagLogVerbose, "verbose", "v", false, "Show all information messages")
app.PersistentFlags().BoolVarP(&globalCmd.flagLogDebug, "debug", "d", false, "Show all debug messages")
app.PersistentFlags().StringVarP(&globalCmd.flagSecretsLocation, "secrets-location", "s", "", "Secrets location of the certificate and private key")
if runtime.GOOS == "windows" {
app.PersistentFlags().BoolVar(&globalCmd.flagService, "service", false, "Start as a system service")
}

View File

@@ -124,7 +124,7 @@ func (c *cmdAgent) Run(cmd *cobra.Command, args []string) error {
// Mount shares from host.
c.mountHostShares()
d := newDaemon(c.global.flagLogDebug, c.global.flagLogVerbose)
d := newDaemon(c.global.flagLogDebug, c.global.flagLogVerbose, c.global.flagSecretsLocation)
// Start the server.
err = startHTTPServer(d, c.global.flagLogDebug)

View File

@@ -41,8 +41,8 @@ func (l *networkListener) Accept() (net.Conn, error) {
return tls.Server(c, l.config), nil
}
func serverTLSConfig() (*tls.Config, error) {
certInfo, err := localtls.KeyPairAndCA(".", "agent", localtls.CertServer, false)
func serverTLSConfig(secretsLocation string) (*tls.Config, error) {
certInfo, err := localtls.KeyPairAndCA(secretsLocation, "agent", localtls.CertServer, false)
if err != nil {
return nil, err
}

View File

@@ -44,7 +44,7 @@ func (m *incusAgentService) Execute(args []string, r <-chan svc.ChangeRequest, c
changes <- svc.Status{State: svc.StartPending}
d := newDaemon(m.agentCmd.global.flagLogDebug, m.agentCmd.global.flagLogVerbose)
d := newDaemon(m.agentCmd.global.flagLogDebug, m.agentCmd.global.flagLogVerbose, m.agentCmd.global.flagSecretsLocation)
// Start the server.
err := startHTTPServer(d, m.agentCmd.global.flagLogDebug)

View File

@@ -1,2 +0,0 @@
@echo off
powershell.exe -ExecutionPolicy Bypass -File "C:\Program Files\Incus-Agent\incus-agent-setup.ps1"

View File

@@ -1,60 +1,83 @@
# Variables setup
# Installation folder in ProgramData
$destFolder = "C:\ProgramData\Incus-Agent"
$agentExecutable = "incus-agent.exe"
$serviceName = "Incus-Agent"
$serviceDisplayName = "Incus Agent Service"
$serviceDescription = "Incus Agent Service"
function ExitSetup {
# Recursively delete the old agent as we only want the agent to run if the CDROM is present.
# Failsafe in case it was not deleted on shutdown.
# Close the firewall.
Remove-NetFirewallRule -Name $serviceName -ErrorAction SilentlyContinue
# Stop the service in case it was running.
Stop-Service $serviceName -Force
# Delete the service.
sc.exe delete $serviceName
# Delete all files
Remove-Item -Path "$destFolder" -Recurse -Force
}
$targetDrive = Get-WmiObject -Class Win32_Volume | Where-Object { $_.Label -eq "incus-agent" }
if (!$targetDrive) {
Write-Host "Drive containing the agent was not found."
Write-Host "Searching if the agent is already installed..."
ExitSetup
}
if (!(Test-Path $destFolder)) {
Write-Host "$destFolder was not found."
exit 1
}
elseif (!(Test-Path "$destFolder\$agentExecutable")) {
Write-Host "$destFolder\$agentExecutable was not found."
exit 1
Write-Host "Drive containing the agent was found: $($targetDrive.DriveLetter)"
if (!(Test-Path $destFolder)) {
Write-Host "Creating $destFolder..."
New-Item -ItemType Directory -Path $destFolder -Force | Out-Null
if (!$?) {
Write-Host "Could not create $destFolder..."
ExitSetup
}
}
Write-Host "Copying the content of the CD-ROM to $destFolder..."
Copy-Item `
-Recurse `
-Path "$($targetDrive.Name)*" `
-Destination $destFolder `
-Exclude '*.ps1', '*.bat' `
-Force
if (!$?) {
Write-Host "Failed to copy the agent files."
ExitSetup
}
Write-Host "Ejecting CD-ROM..."
(New-Object -ComObject Shell.Application).Namespace(17).ParseName($targetDrive.DriveLetter).InvokeVerb("Eject")
# Dumb search for firewall rule assuming the name of the rule is "$serviceName".
if (!(Get-NetFirewallRule -Name "$serviceName" -ErrorAction SilentlyContinue)) {
New-NetFirewallRule -Name "$serviceName" -DisplayName "Allow Port 8443 for Incus-Agent" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 8443
}
$serviceCommand = "`"$destFolder\$agentExecutable`" --service --secrets-location $destFolder"
if (Get-Service -Name $serviceName -ErrorAction SilentlyContinue) {
Write-Host "Service exists. Updating configuration..."
# Do not Out-Null to see if there is any error doing the following command as it is important.
sc.exe config $serviceName binPath= "$serviceCommand" DisplayName= "$serviceDisplayName"
sc.exe description $serviceName "$serviceDescription" | Out-Null
Set-Service -Name $serviceName -StartupType Manual
Write-Host "Service '$serviceName' updated successfully."
}
else {
Write-Host "Drive containing the agent was found: $($targetDrive.DriveLetter)"
Write-Host "Service does not exist. Creating new service..."
if (!(Test-Path $destFolder)) {
Write-Host "Creating $destFolder..."
New-Item -ItemType Directory -Path $destFolder -Force | Out-Null
if (!$?) {
Write-Host "Could not create $destFolder..."
exit 1
}
}
New-Service -Name $serviceName -BinaryPathName $serviceCommand -DisplayName $serviceDisplayName -Description $serviceDescription -StartupType Manual
Write-Host "Copying the content of the CD-ROM to $destFolder..."
Copy-Item `
-Recurse `
-Path "$($targetDrive.Name)*" `
-Destination $destFolder `
-Exclude '*.ps1', '*.bat' `
-Force
if (!$?) {
Write-Host "Failed to copy the agent files."
exit 1
}
Write-Host "Ejecting CD-ROM..."
(New-Object -ComObject Shell.Application).Namespace(17).ParseName($targetDrive.DriveLetter).InvokeVerb("Eject")
Write-Host "Service '$serviceName' created successfully."
}
# By default, services are ran in C:\Windows\System32. Changing the location so the agent finds the certificates and keys.
Set-Location -Path $destFolder
# Dumb search for firewall rule assuming the name of the rule is "incus-agent-service".
if (!(Get-NetFirewallRule -Name "incus-agent-service" -ErrorAction SilentlyContinue)) {
New-NetFirewallRule -Name "incus-agent-service" -DisplayName "Allow Port 8443 for Incus-Agent" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 8443
}
Write-Host "Running the agent..."
& .\incus-agent.exe --service
Restart-Service $serviceName -Force

View File

@@ -37,32 +37,13 @@ if (!$?) {
exit 1
}
$serviceName = "Incus-Agent"
$serviceDisplayName = "Incus Agent Service"
$serviceDescription = "Incus Agent Service"
# Override the scheduled task even if it exists
$destFolder = "C:\Program Files\Incus-Agent"
$taskFile = "$destFolder\incus-agent-setup.ps1"
$taskAction = New-ScheduledTaskAction -Execute "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -Argument "-ExecutionPolicy Bypass -File `"$taskFile`""
$taskTrigger = New-ScheduledTaskTrigger -AtStartup
$taskPrincipal = New-ScheduledTaskPrincipal -UserID "NT AUTHORITY\SYSTEM" -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -Action $taskAction -Trigger $taskTrigger -Principal $taskPrincipal -TaskName "Incus Agent Setup" -Description "Every setup required for the Incus agent including copying the files, opening the firewall, etc." -Force
# Hack to run a PowerShell script as a service without a third party tool.
# The bat file only contains a line to run the actual PowerShell script.
$serviceCommand = "`"$destFolder\incus-agent-setup.bat`""
if (Get-Service -Name $serviceName -ErrorAction SilentlyContinue) {
Write-Host "Service exists. Updating configuration..."
# Do not Out-Null to see if there is any error doing the following command as it is important.
sc.exe config $serviceName binPath= "$serviceCommand" DisplayName= "$serviceDisplayName"
sc.exe description $serviceName "$serviceDescription" | Out-Null
Set-Service -Name $serviceName -StartupType Automatic
Write-Host "Service '$serviceName' updated successfully."
}
else {
Write-Host "Service does not exist. Creating new service..."
New-Service -Name $serviceName -BinaryPathName $serviceCommand -DisplayName $serviceDisplayName -Description $serviceDescription -StartupType Automatic
Write-Host "Service '$serviceName' created successfully."
}
Restart-Service $serviceName -Force
# Start the PowerShell script to simulate start up
& "$taskFile"

View File

@@ -3277,17 +3277,7 @@ func (d *qemu) generateConfigShare() error {
// Setup script for incus-agent that is executed by Service Control Manager (SCM). Since by
// default Windows cannot run a PowerShell script as a service without the help of a third
// party, a bat file is used to then execute the PowerShell script doing the job.
agentFile, err := incusAgentLoader.ReadFile("agent-loader/incus-agent-setup.bat")
if err != nil {
return err
}
err = os.WriteFile(filepath.Join(configDrivePath, "incus-agent-setup.bat"), agentFile, 0o500)
if err != nil {
return err
}
agentFile, err = incusAgentLoader.ReadFile("agent-loader/incus-agent-setup.ps1")
agentFile, err := incusAgentLoader.ReadFile("agent-loader/incus-agent-setup.ps1")
if err != nil {
return err
}