beszel icon indicating copy to clipboard operation
beszel copied to clipboard

[Bug] [Enhancement]: Issues with beszel agent when NSSM is already installed!

Open mattish91 opened this issue 4 months ago • 5 comments

Component

Agent

Description

I have an instance if NSSM already installed on my Windows client (Jellyfin server). and now beszel-agent is also starting the Jellyfin server.

Image

Expected Behavior

beszel-agent gets it's own nssm setup? Or standalone nssm?

My previous working script (i used to build my own agent):

$ProgressPreference = 'SilentlyContinue'
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$pp = "C:\Program Files\beszel-agent"
$url = "https://just.webserver.mydomain.com/files/Monitor"
mkdir "$pp" -EA SilentlyContinue
cd "$pp"
.\nssm.exe stop beszel-agent
Invoke-WebRequest -Uri $url/agent.exe -OutFile "$pp\agent.exe"
Invoke-WebRequest -Uri $url/nssm.exe -OutFile "$pp\nssm.exe"
cd "$pp"
.\nssm.exe install beszelagent "C:\Program Files\beszel-agent\agent.exe"
.\nssm.exe set beszelagent AppEnvironmentExtra "KEY=ssh-ed25519 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
New-NetFirewallRule -DisplayName "beszel-agent" -Direction Inbound -Protocol TCP -LocalPort 45876 -Action Allow
Start-Sleep -seconds 10
.\nssm.exe start beszel-agent
sc start beszel-agent

Steps to Reproduce

  1. Install Jellyfin Server (as a service) on your Windows machine
  2. Install Beszel-Agent trough provided script. (Reports NSSM is already installed).
  3. Check the Beszel-Agent service for result.

Category

Installation

Affected Metrics

Other

OS / Architecture

Windows 11 EN-US x64 26100.4770

Beszel version

0.12.3

Installation method

Docker

Configuration

Beszel server is working fine, really not needed in this case.

Hub Logs


Agent Logs

Information missing as the correct executable never gets started.

mattish91 avatar Aug 04 '25 04:08 mattish91

My solution to this issue looks like this:

$ProgressPreference = 'SilentlyContinue'
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$pp = "C:\Program Files\beszel-agent"
$exeName = "beszel-agent.exe"
$zipFile = "$pp\beszel-agent.zip"
$githubApiUrl = "https://api.github.com/repos/henrygd/beszel/releases/latest"
$nssmZipUrl = "https://nssm.cc/release/nssm-2.24.zip"
$nssmZipFile = "$pp\nssm.zip"
$nssmExtractPath = "$pp\nssm"
$nssmExeTarget = "$pp\nssm.exe"
$oldServices = @("beszelagent", "bezselagent", "beszel-agent", "bezsel-agent")
mkdir "$pp" -EA SilentlyContinue
cd "$pp"
foreach ($svc in $oldServices) {
    try {
        .\nssm.exe stop $svc -ErrorAction SilentlyContinue
        Start-Sleep -Seconds 2
        .\nssm.exe remove $svc confirm -ErrorAction SilentlyContinue
    } catch {}
}
Get-ChildItem -Path "$pp\*" -Recurse -Force | Remove-Item -Force -Recurse -ErrorAction SilentlyContinue
$release = Invoke-RestMethod -Uri $githubApiUrl -Headers @{"User-Agent"="Mozilla/5.0"}
$asset = $release.assets | Where-Object { $_.name -eq "beszel-agent_windows_amd64.zip" }
Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $zipFile
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::ExtractToDirectory($zipFile, $pp)
Remove-Item $zipFile
Invoke-WebRequest -Uri $nssmZipUrl -OutFile $nssmZipFile
[System.IO.Compression.ZipFile]::ExtractToDirectory($nssmZipFile, $nssmExtractPath)
Remove-Item $nssmZipFile
$nssmVersionDir = Get-ChildItem "$nssmExtractPath" -Directory | Where-Object { $_.Name -like "nssm-*" } | Select-Object -First 1
if ($nssmVersionDir) {
    $nssmExeSource = Join-Path $nssmVersionDir.FullName "win64\nssm.exe"
    if (Test-Path $nssmExeSource) {
        Copy-Item $nssmExeSource $nssmExeTarget -Force
    }
}
.\nssm.exe install beszel-agent "$pp\$exeName"
.\nssm.exe set beszel-agent AppEnvironmentExtra "KEY=ssh-ed25519 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
New-NetFirewallRule -DisplayName "beszel-agent" -Direction Inbound -Protocol TCP -LocalPort 45876 -Action Allow
Start-Sleep -Seconds 10
.\nssm.exe start beszel-agent
sc start beszel-agent

This will ensure that the agent gets updated correctly, has it's own included NSSM to not be confused with any other application utilizing any kind of installed or other wise availiable NSSM.exe and also grab the latest version of both nssm directly from nssm.cc and beszel-agent straight from github zip (unless the zip changes name at some point for the x64 version. I suppose this could be customized to be run on x86/arm64 as well trough a few WMI requests to check for OS architecture if needed. And i also suppose it's possible to customize variables for the .ps1 as well, as long as some one knows how to implement that in to the script but that's above my knowledge as of right now.

So far, i have not gotten any virus/malware/not-a-virus:HEUR false positives with either Windows Defender, Kaspersky or Panda 360 EP.

Feel free to elaborate on my personal solution if needed. This also ensures the port to be open on Windows Server (Tested with 2022/2025) and it looks ok. Without the rule it gets blocked by default.

This script was made to mainly be run trough RMM/Administrator/System user with no user interaction/intervention.

Personally i implemented this with an autounattend.xml that grabs a set of applications and other scripts to always be installed on fresh setups and trough RMM (Immense/Remotely) Script section.

mattish91 avatar Aug 04 '25 21:08 mattish91

Thanks, not sure why that would happen but I'll look further into it. I assume it has something to do with calling nssm.exe and the jellyfin executable also being named nssm.exe, if I'm interpreting that screenshot correctly. I don't have a Windows background so I'm still learning exactly how this works.

Your new script should continue to work fine. We also support agent initiated connections now with HUB_URL and TOKEN, so you may not even need the firewall rule.

The build process for Windows did change because we added LibreHardwareMonitorLib. I just make a note about this in the docs: https://beszel.dev/guide/compiling

henrygd avatar Aug 05 '25 01:08 henrygd

When Jellyfin get's installed as a service on Windows, it automatically installs NSSM to setup the service, it then tries to utilize that NSSM installation for beszel but that for some reason just point's it back at Jellyfin, not entirely sure how that NSSM was built either so i can't really answer for that specifically.

I just grabbed a copy of the current PS1 script and ill see if i can modify it to some extent to utilize the variables, it would then make it much easier to install for everyone. Basically something like this: irm https://whateverurl.com/install-agent.ps1 | iex

Or at least that's how i would imagine the Windows setup to go to make it both fast and smooth for every one.

mattish91 avatar Aug 05 '25 12:08 mattish91

Alright finally!

Old comment was too confusing (way too many edits as im tired), so here we go again xD

After alot of tinkering around, i ended up making an array for the AppEnvironmentExtra to stick with the agent, not entirely sure why my old script worked putting them in one by one, but now it works!

param (
    [switch]$Elevated,
    [Parameter(Mandatory = $true)]
    [string]$Key,
    [string]$Token = "",
    [string]$Url = "",
    [int]$Port = 45876,
    [string]$AgentPath = "$env:ProgramFiles\beszel-agent"
)

# Elevation check
if (-not $Elevated -and -not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
    Write-Host "Re-launching with elevated privileges..." -ForegroundColor Yellow
    $argList = "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`" -Elevated -Key `"$Key`" -Token `"$Token`" -Url `"$Url`" -Port $Port"
    Start-Process powershell -Verb RunAs -ArgumentList $argList
    exit
}

$ErrorActionPreference = "Stop"
$ProgressPreference = 'SilentlyContinue'
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

# Paths
$exeName = "beszel-agent.exe"
$zipFile = "$AgentPath\beszel-agent.zip"
$nssmZipUrl = "https://nssm.cc/release/nssm-2.24.zip"
$nssmZipFile = "$AgentPath\nssm.zip"
$nssmExtractPath = "$AgentPath\nssm"
$nssmExeTarget = "$AgentPath\nssm.exe"
$nssmCommand = "$AgentPath\nssm.exe"

# Cleanup old services
$oldServices = @("beszelagent", "bezselagent", "beszel-agent", "bezsel-agent")
mkdir "$AgentPath" -EA SilentlyContinue | Out-Null
cd "$AgentPath"
foreach ($svc in $oldServices) {
    try {
        & $nssmCommand stop $svc -ErrorAction SilentlyContinue
        Start-Sleep -Seconds 2
        & $nssmCommand remove $svc confirm -ErrorAction SilentlyContinue
    } catch {}
}
Get-ChildItem -Path "$AgentPath\*" -Recurse -Force | Remove-Item -Force -Recurse -ErrorAction SilentlyContinue

# Download agent
$githubApiUrl = "https://api.github.com/repos/henrygd/beszel/releases/latest"
$release = Invoke-RestMethod -Uri $githubApiUrl -Headers @{"User-Agent" = "Mozilla/5.0"}
$asset = $release.assets | Where-Object { $_.name -eq "beszel-agent_windows_amd64.zip" }
Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $zipFile
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::ExtractToDirectory($zipFile, $AgentPath)
Remove-Item $zipFile

# Download NSSM
Invoke-WebRequest -Uri $nssmZipUrl -OutFile $nssmZipFile
[System.IO.Compression.ZipFile]::ExtractToDirectory($nssmZipFile, $nssmExtractPath)
Remove-Item $nssmZipFile
$nssmVersionDir = Get-ChildItem "$nssmExtractPath" -Directory | Where-Object { $_.Name -like "nssm-*" } | Select-Object -First 1
if ($nssmVersionDir) {
    $nssmExeSource = Join-Path $nssmVersionDir.FullName "win64\nssm.exe"
    if (Test-Path $nssmExeSource) {
        Copy-Item $nssmExeSource $nssmExeTarget -Force
    }
}

# Clean up nssm extraction path
Remove-Item -Path $nssmExtractPath -Recurse -Force

# Install service
& $nssmCommand install beszel-agent "$AgentPath\$exeName"

# Set agent variables
$envVars = @(
    "KEY=$Key",
    "TOKEN=$Token",
    "HUB_URL=$Url",
    "PORT=$Port"
)
$envString = $envVars -join " "
& $nssmCommand set beszel-agent AppEnvironmentExtra $envString

# Configure logging
$logDir = "$env:ProgramData\beszel-agent\logs"
if (-not (Test-Path $logDir)) {
    New-Item -ItemType Directory -Path $logDir -Force | Out-Null
}
$logFile = "$logDir\beszel-agent.log"
& $nssmCommand set beszel-agent AppStdout $logFile
& $nssmCommand set beszel-agent AppStderr $logFile

# Firewall rule
New-NetFirewallRule -DisplayName "beszel-agent" -Direction Inbound -Protocol TCP -LocalPort $Port -Action Allow

# Start service
Start-Sleep -Seconds 5
& $nssmCommand start beszel-agent
sc start beszel-agent

Write-Host "Success! beszel-agent installed and running" -ForegroundColor Green

And i basically execute the script exactly like it is now:

& iwr -useb https://test.dev.mspot.se -OutFile "$env:TEMP\install-agent.ps1"; & Powershell -ExecutionPolicy Bypass -File "$env:TEMP\install-agent.ps1" -Key "ssh-ed25519 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" -Port 45876 -Token "0A0A00A0-A000-000A-A0A0-0000A0A000AA" -Url "https://YourServerURL"

It might need some things like checking if the firewall rule is already active, delete it if it is? and then re-add the rule and such. But at least it's a somewhat simple script that is easy to understand and should work with both Windows Server 2019 - 2025 and Windows 1X.

Note that i left my url for the server script so you could test it out if need be, it should* work.

Here is my output:

Can't open service!
OpenService(): Service not installed.

Can't open service!
OpenService(): Service not installed.

Can't open service!
OpenService(): Service not installed.

Can't open service!
OpenService(): Service not installed.

beszel-agent: STOP: The action has been completed.
Service "beszel-agent" removed successfully!
Can't open service!
OpenService(): Service not installed.

Can't open service!
OpenService(): Service not installed.

Service "beszel-agent" installed successfully!
Set parameter "AppEnvironmentExtra" for service "beszel-agent".
Set parameter "AppEnvironmentExtra" for service "beszel-agent".
Set parameter "AppEnvironmentExtra" for service "beszel-agent".
Set parameter "AppEnvironmentExtra" for service "beszel-agent".
Set parameter "AppStdout" for service "beszel-agent".
Set parameter "AppStderr" for service "beszel-agent".


Name                          : {086b26c5-f9dc-428c-a19f-765e5d5ec649}
DisplayName                   : beszel-agent
Description                   :
DisplayGroup                  :
Group                         :
Enabled                       : True
Profile                       : Any
Platform                      : {}
Direction                     : Inbound
Action                        : Allow
EdgeTraversalPolicy           : Block
LooseSourceMapping            : False
LocalOnlyMapping              : False
Owner                         :
PrimaryStatus                 : OK
Status                        : The rule was parsed successfully from the store. (65536)
EnforcementStatus             : NotApplicable
PolicyStoreSource             : PersistentStore
PolicyStoreSourceType         : Local
RemoteDynamicKeywordAddresses : {}
PolicyAppId                   :
PackageFamilyName             :

beszel-agent:  START: The action has been completed.
Success! beszel-agent installed and running

It grabs all the things needed for the installation, insert's the AppEnvironmentExtra including the log parameters Maybe some other PS guru is out there to refine this?

mattish91 avatar Aug 08 '25 23:08 mattish91

Made the one-liner install script even shorter ^^

Original (325 characters):

& iwr -useb https://test.dev.mspot.se -OutFile "$env:TEMP\install-agent.ps1"; & Powershell -ExecutionPolicy Bypass -File "$env:TEMP\install-agent.ps1" -Key "ssh-ed25519 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" -Port 45876 -Token "0A0A00A0-A000-000A-A0A0-0000A0A000AA" -Url "https://YourServerURL"

Shortened (274 characters):

$t="$env:TEMP\i.ps1"; iwr -useb https://test.dev.mspot.se -OutFile $t; powershell -ep Bypass -f $t --Key "ssh-ed25519 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" -Port 45876 -Token "0A0A00A0-A000-000A-A0A0-0000A0A000AA" -Url "https://YourServerURL"

mattish91 avatar Aug 08 '25 23:08 mattish91