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:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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{}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
@echo off
|
||||
powershell.exe -ExecutionPolicy Bypass -File "C:\Program Files\Incus-Agent\incus-agent-setup.ps1"
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user