diff --git a/install.ps1 b/install.ps1 index 03876cb..469c98a 100644 --- a/install.ps1 +++ b/install.ps1 @@ -1,26 +1,26 @@ # Windows PowerShell installation script for Komari Agent # Logging functions with colors -function Log-Info { Write-Host "[INFO] $($_)" -ForegroundColor Cyan } -function Log-Success{ Write-Host "[SUCCESS] $($_)" -ForegroundColor Green } -function Log-Warning{ Write-Host "[WARNING] $($_)" -ForegroundColor Yellow } -function Log-Error { Write-Host "[ERROR] $($_)" -ForegroundColor Red } -function Log-Step { Write-Host "[STEP] $($_)" -ForegroundColor Magenta } -function Log-Config { Write-Host "[CONFIG] $($_)" -ForegroundColor White } +function Log-Info { param([string]$Message) Write-Host "$Message" -ForegroundColor Cyan } +function Log-Success { param([string]$Message) Write-Host "$Message" -ForegroundColor Green } +function Log-Warning { param([string]$Message) Write-Host "[WARNING] $Message" -ForegroundColor Yellow } +function Log-Error { param([string]$Message) Write-Host "[ERROR] $Message" -ForegroundColor Red } +function Log-Step { param([string]$Message) Write-Host "$Message" -ForegroundColor Magenta } +function Log-Config { param([string]$Message) Write-Host "- $Message" -ForegroundColor White } # Default parameters -$InstallDir = Join-Path $Env:ProgramFiles "Komari" -$ServiceName = "komari-agent" -$GitHubProxy = "" -$KomariArgs = @() +$InstallDir = Join-Path $Env:ProgramFiles "Komari" +$ServiceName = "komari-agent" +$GitHubProxy = "" +$KomariArgs = @() # Parse script arguments for ($i = 0; $i -lt $args.Count; $i++) { switch ($args[$i]) { - "--install-dir" { $InstallDir = $args[$i+1]; $i++; continue } - "--install-service-name" { $ServiceName = $args[$i+1]; $i++; continue } - "--install-ghproxy" { $GitHubProxy = $args[$i+1]; $i++; continue } - Default { $KomariArgs += $args[$i] } + "--install-dir" { $InstallDir = $args[$i + 1]; $i++; continue } + "--install-service-name" { $ServiceName = $args[$i + 1]; $i++; continue } + "--install-ghproxy" { $GitHubProxy = $args[$i + 1]; $i++; continue } + Default { $KomariArgs += $args[$i] } } } @@ -41,6 +41,118 @@ switch ($env:PROCESSOR_ARCHITECTURE) { Default { Log-Error "Unsupported architecture: $env:PROCESSOR_ARCHITECTURE"; exit 1 } } +# Ensure installation directory exists for nssm and agent +Log-Step "Ensuring installation directory exists: $InstallDir" +New-Item -ItemType Directory -Path $InstallDir -Force -ErrorAction SilentlyContinue | Out-Null # Ensure $InstallDir exists + +# Check for nssm and download if not present +$nssmExeToUse = Join-Path $InstallDir "nssm.exe" + +# First, check if nssm is in PATH and is functional +$nssmCmd = Get-Command nssm -ErrorAction SilentlyContinue +if ($nssmCmd) { + Log-Info "nssm found in PATH at $($nssmCmd.Source)." + try { + $nssmVersionOutput = nssm version 2>&1 + Log-Info "Detected nssm version: $nssmVersionOutput" + } + catch { + Log-Warning "nssm found in PATH failed to execute 'nssm version'. Will attempt to use/download local copy. Error: $_" + $nssmCmd = $null # Force re-evaluation for local copy or download + } +} + +# If nssm not found in PATH or the one in PATH failed, check local $InstallDir +if (-not $nssmCmd) { + if (Test-Path $nssmExeToUse) { + Log-Info "nssm found at $nssmExeToUse. Attempting to use it by adding $InstallDir to PATH." + $env:Path = "$($InstallDir);$($env:Path)" + $nssmCmd = Get-Command nssm -ErrorAction SilentlyContinue + if ($nssmCmd) { + try { + $nssmVersionOutput = nssm version 2>&1 + } + catch { + Log-Warning "nssm from $InstallDir failed to execute 'nssm version'. Error: $_" + $nssmCmd = $null # Mark as unusable + } + } + else { + Log-Warning "Failed to make nssm from $nssmExeToUse available via PATH. Will attempt download." + } + } +} + +# If still no usable nssm command, proceed to download +if (-not $nssmCmd) { + Log-Info "nssm not found or not usable. Attempting to download to $InstallDir..." + $NssmVersion = "2.24" + $NssmZipUrl = "https://nssm.cc/release/nssm-$NssmVersion.zip" + $TempNssmZipPath = Join-Path $env:TEMP "nssm-$NssmVersion.zip" + $TempExtractDir = Join-Path $env:TEMP "nssm_extract_temp" + + try { + Log-Info "Downloading nssm from $NssmZipUrl..." + Invoke-WebRequest -Uri $NssmZipUrl -OutFile $TempNssmZipPath -UseBasicParsing + + if (Test-Path $TempExtractDir) { Remove-Item -Recurse -Force $TempExtractDir } + New-Item -ItemType Directory -Path $TempExtractDir -Force | Out-Null + Expand-Archive -Path $TempNssmZipPath -DestinationPath $TempExtractDir -Force + + $NssmSourceDirInsideZip = "nssm-$NssmVersion" # Used for Get-ChildItem search path + # The path part within the extracted nssm folder, e.g., "nssm-2.24\win32" + # 'win32' nssm is used for both 'amd64' and 'arm64' PowerShell architectures. + $NssmArchSubDir = Join-Path "nssm-$NssmVersion" "win32" + $NssmSourceExePath = Join-Path (Join-Path $TempExtractDir $NssmArchSubDir) "nssm.exe" + + if (-not (Test-Path $NssmSourceExePath)) { + Log-Error "Could not find nssm.exe at expected path: $NssmSourceExePath after extraction." + # Fallback search for nssm.exe within the extracted directory + $foundNssmFallback = Get-ChildItem -Path $TempExtractDir -Recurse -Filter "nssm.exe" | + Where-Object { $_.FullName -like "*$NssmArchSubDir\nssm.exe" } | + Select-Object -First 1 + if ($foundNssmFallback) { + Log-Warning "Found nssm.exe at $($foundNssmFallback.FullName) using fallback search. Using this." + $NssmSourceExePath = $foundNssmFallback.FullName + } + else { + Log-Error "nssm.exe ($NssmArchSubDir) still not found in $TempExtractDir. Please install nssm manually (from https://nssm.cc) and ensure it's in your PATH." + exit 1 + } + } + + Copy-Item -Path $NssmSourceExePath -Destination $nssmExeToUse -Force + + $env:Path = "$($InstallDir);$($env:Path)" + $nssmCmd = Get-Command nssm -ErrorAction SilentlyContinue # Re-check after adding to PATH + if ($nssmCmd) { + Log-Success "Downloaded nssm is now configured and available in PATH." + } + else { + Log-Error "Failed to configure downloaded nssm in PATH from $nssmExeToUse. Please ensure $InstallDir is in your system PATH or nssm is installed globally." + exit 1 + } + } + catch { + Log-Error "Failed to download or configure nssm: $_" + Log-Error "Please install nssm manually from https://nssm.cc and ensure nssm.exe is in your PATH." + exit 1 + } + finally { + if (Test-Path $TempNssmZipPath) { Remove-Item $TempNssmZipPath -Force -ErrorAction SilentlyContinue } + if (Test-Path $TempExtractDir) { Remove-Item $TempExtractDir -Recurse -Force -ErrorAction SilentlyContinue } + } +} + +# Final check that nssm is operational +try { + $nssmVersionOutput = nssm version 2>&1 +} +catch { + Log-Error "nssm command failed to execute even after setup attempts. Please check the nssm installation and PATH. Error: $_" + exit 1 +} + Log-Step "Installation configuration:" Log-Config "Service name: $ServiceName" Log-Config "Install directory: $InstallDir" @@ -48,21 +160,39 @@ Log-Config "GitHub proxy: $ProxyDisplay" Log-Config "Agent arguments: $($KomariArgs -join ' ')" # Paths -$BinaryName = "komari-agent-windows-$arch.exe" -$AgentPath = Join-Path $InstallDir $BinaryName +$BinaryName = "komari-agent-windows-$arch.exe" +$AgentPath = Join-Path $InstallDir "komari-agent.exe" # Uninstall previous service and binary function Uninstall-Previous { Log-Step "Checking for existing service..." - $svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue - if ($svc) { + # Check if service exists using nssm status, as Get-Service might not work for nssm services if not properly registered + $serviceStatus = nssm status $ServiceName 2>&1 + if ($serviceStatus -notmatch "SERVICE_STOPPED" -and $serviceStatus -notmatch "does not exist") { Log-Info "Stopping service $ServiceName..." - Stop-Service $ServiceName -Force - Log-Info "Deleting service $ServiceName..." - sc.exe delete $ServiceName | Out-Null + nssm stop $ServiceName 2>&1 | Out-Null } + # Attempt to remove the service using nssm + # We check if it exists first by trying to get its status. + # nssm remove will succeed if the service exists, and fail otherwise. + # We add confirm to avoid interactive prompts. + $removeOutput = nssm remove $ServiceName confirm 2>&1 + if ($LASTEXITCODE -eq 0) { + } + elseif ($removeOutput -match "Can't open service! (The specified service does not exist as an installed service.)" -or $removeOutput -match "No such service" -or $removeOutput -match "does not exist") { + Log-Info "Service $ServiceName does not exist or was already removed." + } + else { + # If nssm remove fails for other reasons, try sc.exe delete as a fallback for older installations + $svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue + if ($svc) { + Stop-Service $ServiceName -Force -ErrorAction SilentlyContinue + sc.exe delete $ServiceName | Out-Null + } + } + if (Test-Path $AgentPath) { - Log-Info "Removing old binary..." + Log-Warning "Removing old binary..." Remove-Item $AgentPath -Force } } @@ -70,39 +200,46 @@ Uninstall-Previous # Fetch latest release version $ApiUrl = "https://api.github.com/repos/komari-monitor/komari-agent/releases/latest" -Log-Step "Fetching latest version from GitHub API..." try { - $release = Invoke-RestMethod -Uri $ApiUrl -UseBasicParsing - $latestVersion = $release.tag_name -} catch { + $release = Invoke-RestMethod -Uri $ApiUrl -UseBasicParsing + $latestVersion = $release.tag_name +} +catch { Log-Error "Failed to fetch latest version: $_" exit 1 } Log-Success "Latest version: $latestVersion" # Construct download URL -$BinaryName = "komari-agent-windows-$arch.exe" +$BinaryName = "komari-agent-windows-$arch.exe" $DownloadUrl = if ($GitHubProxy) { "$GitHubProxy/https://github.com/komari-monitor/komari-agent/releases/download/$latestVersion/$BinaryName" } else { "https://github.com/komari-monitor/komari-agent/releases/download/$latestVersion/$BinaryName" } # Download and install -Log-Step "Preparing installation directory..." New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null -Log-Step "Downloading $BinaryName..." Log-Info "URL: $DownloadUrl" try { Invoke-WebRequest -Uri $DownloadUrl -OutFile $AgentPath -UseBasicParsing -} catch { +} +catch { Log-Error "Download failed: $_" exit 1 } Log-Success "Downloaded and saved to $AgentPath" # Register and start service -Log-Step "Configuring Windows service..." +Log-Step "Configuring Windows service with nssm..." $argString = $KomariArgs -join ' ' -New-Service -Name $ServiceName -BinaryPathName "`"$AgentPath`" $argString" -DisplayName "Komari Agent Service" -StartupType Automatic -Start-Service $ServiceName -Log-Success "Service $ServiceName installed and started." +# Ensure InstallDir and AgentPath are quoted if they contain spaces +$quotedAgentPath = "`"$AgentPath`"" +nssm install $ServiceName $quotedAgentPath $argString +# Set display name and startup type using nssm +nssm set $ServiceName DisplayName "Komari Agent Service" +nssm set $ServiceName Start SERVICE_AUTO_START +nssm set $ServiceName AppExit Default Restart +nssm set $ServiceName AppRestartDelay 5000 +# Start the service using nssm +nssm start $ServiceName +Log-Success "Service $ServiceName installed and started using nssm." Log-Success "Komari Agent installation completed!" Log-Config "Service name: $ServiceName"