Compare commits

...

3 Commits

Author SHA1 Message Date
Martin
946f2e6be1 Report the configured listen address after install (#2243)
The final message always echoed $PORT, which falls back to the default when -p
is not passed. Existing service files are kept as they are, so a plain upgrade
on a host with a custom port reported that the agent runs on 45876 regardless
of the actual configuration.

Read the address from the active service file instead. LISTEN is checked before
PORT to match the agent's own precedence in GetAddress, and the value is read as
text so host:port and unix socket paths are reported as configured.
2026-08-19 11:48:01 -04:00
Sven van Ginkel
ba90daf4d6 fix(scripts): update agent env vars on reinstall instead of skipping (#2107)
Co-authored-by: henrygd <hank@henrygd.me>
2026-08-19 11:37:11 -04:00
Toomore Chiang
aa1d67a122 fix(agent): strip invalid UTF-8 from battery names (#2241)
Battery names come from firmware (sysfs model_name on Linux), which does not
guarantee valid UTF-8. The hub decodes agent payloads using the default
fxamacker/cbor decode mode, which rejects invalid UTF-8, so a single bad byte
in a battery name makes the hub drop the entire payload and mark the system
down until the agent is downgraded.
2026-08-19 11:02:33 -04:00
4 changed files with 154 additions and 21 deletions

View File

@@ -33,7 +33,10 @@ var errNoBatteries = errors.New("no readable batteries")
func normalizeBatteries(batteries []Battery) []Battery {
nameCounts := make(map[string]int, len(batteries))
for i := range batteries {
name := strings.TrimSpace(batteries[i].Name)
// Names come from firmware (e.g. sysfs model_name) and are not guaranteed to
// be valid UTF-8. Invalid bytes are rejected when the hub decodes the CBOR
// payload, which drops every metric for the system, so strip them here.
name := strings.TrimSpace(strings.ToValidUTF8(batteries[i].Name, ""))
if name == "" {
name = "Battery " + strconv.Itoa(i+1)
}

View File

@@ -2,6 +2,7 @@ package battery
import (
"testing"
"unicode/utf8"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -33,3 +34,15 @@ func TestNormalizeBatteriesFallbackNames(t *testing.T) {
bats := normalizeBatteries([]Battery{{}, {}, {Name: "Mouse"}, {Name: "Mouse"}})
assert.Equal(t, []string{"Battery 1", "Battery 2", "Mouse", "Mouse (2)"}, []string{bats[0].Name, bats[1].Name, bats[2].Name, bats[3].Name})
}
func TestNormalizeBatteriesStripsInvalidUTF8(t *testing.T) {
// Firmware occasionally reports names that are not valid UTF-8 (a ThinkPad
// reporting "LNV-5B11K63024@\xd0" in model_name is a real example).
bats := normalizeBatteries([]Battery{{Name: "LNV-5B11K63024@\xd0"}, {Name: "\xff\xfe"}})
assert.Equal(t, "LNV-5B11K63024@", bats[0].Name)
// A name made up entirely of invalid bytes falls back to the generic name.
assert.Equal(t, "Battery 2", bats[1].Name)
for _, b := range bats {
assert.True(t, utf8.ValidString(b.Name))
}
}

View File

@@ -9,9 +9,21 @@ param (
[string]$NSSMPath = "",
[switch]$ConfigureFirewall,
[ValidateSet("Auto", "Scoop", "WinGet")]
[string]$InstallMethod = "Auto"
[string]$InstallMethod = "Auto",
# Set automatically from $PSBoundParameters below, or forwarded through an elevated relaunch.
# Used so a reinstall only overwrites Token/Url/Port on an existing service if the caller
# actually asked to change them, instead of wiping them with their unset defaults.
[switch]$TokenProvided,
[switch]$UrlProvided,
[switch]$PortProvided
)
if (-not $Elevated) {
$TokenProvided = $PSBoundParameters.ContainsKey('Token')
$UrlProvided = $PSBoundParameters.ContainsKey('Url')
$PortProvided = $PSBoundParameters.ContainsKey('Port')
}
# Check if required parameters are provided
if ([string]::IsNullOrWhiteSpace($Key)) {
Write-Host "ERROR: SSH Key is required." -ForegroundColor Red
@@ -312,7 +324,10 @@ function Install-NSSMService {
[string]$HubUrl = "",
[Parameter(Mandatory=$true)]
[int]$Port,
[string]$NSSMPath = ""
[string]$NSSMPath = "",
[switch]$TokenProvided,
[switch]$UrlProvided,
[switch]$PortProvided
)
Write-Host "Installing beszel-agent service..."
@@ -330,15 +345,26 @@ function Install-NSSMService {
$existingService = Get-Service -Name "beszel-agent" -ErrorAction SilentlyContinue
if ($existingService) {
Write-Host "Service already exists. Checking if path update is needed..."
# Get current service path
# Get current service path
$pathNeedsUpdate = $true
try {
$currentPath = & $nssmCommand get beszel-agent Application
if ($LASTEXITCODE -eq 0 -and $currentPath.Trim() -eq $AgentPath) {
Write-Host "Service already configured with correct path. Skipping service recreation." -ForegroundColor Green
Write-Host "Service path is already correct. Updating environment variables..."
& $nssmCommand set beszel-agent AppEnvironmentExtra "+KEY=$Key"
if ($TokenProvided) { & $nssmCommand set beszel-agent AppEnvironmentExtra "+TOKEN=$Token" }
if ($UrlProvided) { & $nssmCommand set beszel-agent AppEnvironmentExtra "+HUB_URL=$HubUrl" }
if ($PortProvided) { & $nssmCommand set beszel-agent AppEnvironmentExtra "+PORT=$Port" }
# Restart the service so the running process picks up the new environment variables
if ($existingService.Status -eq "Running") {
Write-Host "Restarting service to apply updated environment variables..."
& $nssmCommand restart beszel-agent
}
return
}
Write-Host "Service path needs updating. Stopping and removing existing service..."
Write-Host " Current path: $($currentPath.Trim())"
Write-Host " New path: $AgentPath"
@@ -346,7 +372,7 @@ function Install-NSSMService {
Write-Host "Could not retrieve current service path, will recreate service: $($_.Exception.Message)" -ForegroundColor Yellow
Write-Host "Service path needs updating. Stopping and removing existing service..."
}
try {
& $nssmCommand stop beszel-agent
& $nssmCommand remove beszel-agent confirm
@@ -589,6 +615,12 @@ try {
$argumentList += "`"$NSSMPath`""
}
# Forward which optional values were explicitly provided, so the elevated
# instance knows whether to overwrite them on an existing service
if ($TokenProvided) { $argumentList += "-TokenProvided" }
if ($UrlProvided) { $argumentList += "-UrlProvided" }
if ($PortProvided) { $argumentList += "-PortProvided" }
if ($ConfigureFirewall) {
$argumentList += "-ConfigureFirewall"
}
@@ -601,7 +633,7 @@ try {
# Third: If we have admin rights, install service and configure firewall
if ($isAdmin -or $Elevated) {
# Install the service
Install-NSSMService -AgentPath $AgentPath -Key $Key -Token $Token -HubUrl $Url -Port $Port -NSSMPath $NSSMPath
Install-NSSMService -AgentPath $AgentPath -Key $Key -Token $Token -HubUrl $Url -Port $Port -NSSMPath $NSSMPath -TokenProvided:$TokenProvided -UrlProvided:$UrlProvided -PortProvided:$PortProvided
if ($ConfigureFirewall) {
Configure-Firewall -Port $Port

View File

@@ -97,6 +97,37 @@ ensure_trailing_slash() {
fi
}
# Read the listen address from the active service configuration. Existing
# service files are kept as they are, so the configured address can differ from
# $PORT, which falls back to the default when -p is not passed. LISTEN is
# checked before PORT to match the agent's own precedence, and the value is read
# as text so host:port and unix socket paths survive.
configured_address() {
if is_alpine || is_openwrt; then
address_file=/etc/init.d/beszel-agent
elif is_freebsd; then
address_file="$AGENT_DIR/env"
else
address_file=/etc/systemd/system/beszel-agent.service
fi
[ -f "$address_file" ] || return 0
address_value=$(sed -n 's/.*LISTEN="\{0,1\}\([^"]*\)"\{0,1\}.*/\1/p' "$address_file" | head -n 1)
if [ -z "$address_value" ]; then
address_value=$(sed -n 's/.*PORT="\{0,1\}\([^"]*\)"\{0,1\}.*/\1/p' "$address_file" | head -n 1)
fi
printf '%s\n' "$address_value"
}
# Escape text for use in the replacement portion of a sed s command whose
# delimiter is |. This only escapes sed replacement metacharacters; quoting
# for the destination configuration syntax is handled separately.
escape_sed_replacement() {
printf '%s' "$1" | sed 's/[\\&|]/\\&/g'
}
# Generate FreeBSD rc service content
generate_freebsd_rc_service() {
cat <<'EOF'
@@ -264,6 +295,12 @@ KEY=""
TOKEN=""
HUB_URL=""
AUTO_UPDATE_FLAG="" # empty string means prompt, "true" means auto-enable, "false" means skip
# Track which of the reconfigurable values were explicitly passed as arguments,
# so a reinstall only overwrites the fields the caller actually asked to change.
KEY_PROVIDED=false
PORT_PROVIDED=false
TOKEN_PROVIDED=false
HUB_URL_PROVIDED=false
VERSION="latest"
# Check for help flag
@@ -294,10 +331,10 @@ build_sudo_args() {
if [ -n "$QUOTED_ARGS" ]; then
QUOTED_ARGS="$QUOTED_ARGS "
fi
QUOTED_ARGS="$QUOTED_ARGS'$(echo "$1" | sed "s/'/'\\\\''/g")'"
QUOTED_ARGS="$QUOTED_ARGS'$(printf '%s' "$1" | sed "s/'/'\\\\''/g")'"
shift
done
echo "$QUOTED_ARGS"
printf '%s\n' "$QUOTED_ARGS"
}
# Check if running as root and re-execute with sudo if needed
@@ -319,18 +356,22 @@ while [ $# -gt 0 ]; do
-k)
shift
KEY="$1"
KEY_PROVIDED=true
;;
-p)
shift
PORT="$1"
PORT_PROVIDED=true
;;
-t)
shift
TOKEN="$1"
TOKEN_PROVIDED=true
;;
-url)
shift
HUB_URL="$1"
HUB_URL_PROVIDED=true
;;
-v | --version)
shift
@@ -566,7 +607,7 @@ if [ -z "$KEY" ]; then
fi
# Remove newlines from KEY
KEY=$(echo "$KEY" | tr -d '\n')
KEY=$(printf '%s' "$KEY" | tr -d '\n')
# TOKEN and HUB_URL are optional for backwards compatibility - no interactive prompts
# They will be set as empty environment variables if not provided
@@ -804,7 +845,15 @@ EOF
chmod +x /etc/init.d/beszel-agent
rc-update add beszel-agent default
else
echo "Alpine OpenRC service file already exists. Skipping creation."
echo "Alpine OpenRC service file already exists. Updating environment variables..."
SED_PORT=$(escape_sed_replacement "$PORT")
SED_KEY=$(escape_sed_replacement "$KEY")
SED_TOKEN=$(escape_sed_replacement "$TOKEN")
SED_HUB_URL=$(escape_sed_replacement "$HUB_URL")
[ "$PORT_PROVIDED" = "true" ] && sed -i "s|^export PORT=.*|export PORT=\"$SED_PORT\"|" /etc/init.d/beszel-agent
[ "$KEY_PROVIDED" = "true" ] && sed -i "s|^export KEY=.*|export KEY=\"$SED_KEY\"|" /etc/init.d/beszel-agent
[ "$TOKEN_PROVIDED" = "true" ] && sed -i "s|^export TOKEN=.*|export TOKEN=\"$SED_TOKEN\"|" /etc/init.d/beszel-agent
[ "$HUB_URL_PROVIDED" = "true" ] && sed -i "s|^export HUB_URL=.*|export HUB_URL=\"$SED_HUB_URL\"|" /etc/init.d/beszel-agent
fi
# Create log files with proper permissions
@@ -886,7 +935,24 @@ EOF
chmod +x /etc/init.d/beszel-agent
/etc/init.d/beszel-agent enable
else
echo "OpenWRT init script already exists. Skipping creation."
echo "OpenWRT init script already exists. Updating environment variables..."
# The env vars live on a single procd_set_param line, so merge any values
# that weren't explicitly provided in from the existing line before rewriting it.
CUR_ENV_LINE=$(sed -n '/^[[:space:]]*procd_set_param env PORT=/{p;q;}' /etc/init.d/beszel-agent)
if [ -z "$CUR_ENV_LINE" ] || ! printf '%s\n' "$CUR_ENV_LINE" | grep -q 'PORT="[^"]*" KEY="[^"]*" TOKEN="[^"]*" HUB_URL="[^"]*"'; then
echo "Error: Could not parse the existing environment configuration in /etc/init.d/beszel-agent."
echo "Expected a procd_set_param env line containing PORT, KEY, TOKEN, and HUB_URL."
exit 1
fi
[ "$PORT_PROVIDED" = "true" ] || PORT=$(printf '%s\n' "$CUR_ENV_LINE" | sed -n 's/.*PORT="\([^"]*\)".*/\1/p')
[ "$KEY_PROVIDED" = "true" ] || KEY=$(printf '%s\n' "$CUR_ENV_LINE" | sed -n 's/.*KEY="\([^"]*\)".*/\1/p')
[ "$TOKEN_PROVIDED" = "true" ] || TOKEN=$(printf '%s\n' "$CUR_ENV_LINE" | sed -n 's/.*TOKEN="\([^"]*\)".*/\1/p')
[ "$HUB_URL_PROVIDED" = "true" ] || HUB_URL=$(printf '%s\n' "$CUR_ENV_LINE" | sed -n 's/.*HUB_URL="\([^"]*\)".*/\1/p')
SED_PORT=$(escape_sed_replacement "$PORT")
SED_KEY=$(escape_sed_replacement "$KEY")
SED_TOKEN=$(escape_sed_replacement "$TOKEN")
SED_HUB_URL=$(escape_sed_replacement "$HUB_URL")
sed -i "s|procd_set_param env PORT=.*|procd_set_param env PORT=\"$SED_PORT\" KEY=\"$SED_KEY\" TOKEN=\"$SED_TOKEN\" HUB_URL=\"$SED_HUB_URL\"|" /etc/init.d/beszel-agent
fi
# Start the service
@@ -929,17 +995,25 @@ elif is_freebsd; then
# Ensure rc.d directory exists on minimal FreeBSD installs
mkdir -p /usr/local/etc/rc.d
# Create environment configuration file with proper permissions if it doesn't exist
if [ ! -f "$AGENT_DIR/env" ]; then
echo "Creating environment configuration file..."
# Create or update environment configuration file
if [ -f "$AGENT_DIR/env" ]; then
echo "Environment configuration file already exists. Updating environment variables..."
SED_PORT=$(escape_sed_replacement "$PORT")
SED_KEY=$(escape_sed_replacement "$KEY")
SED_TOKEN=$(escape_sed_replacement "$TOKEN")
SED_HUB_URL=$(escape_sed_replacement "$HUB_URL")
[ "$PORT_PROVIDED" = "true" ] && sed -i '' -e "s|^LISTEN=.*|LISTEN=$SED_PORT|" "$AGENT_DIR/env"
[ "$KEY_PROVIDED" = "true" ] && sed -i '' -e "s|^KEY=.*|KEY=\"$SED_KEY\"|" "$AGENT_DIR/env"
[ "$TOKEN_PROVIDED" = "true" ] && sed -i '' -e "s|^TOKEN=.*|TOKEN=$SED_TOKEN|" "$AGENT_DIR/env"
[ "$HUB_URL_PROVIDED" = "true" ] && sed -i '' -e "s|^HUB_URL=.*|HUB_URL=$SED_HUB_URL|" "$AGENT_DIR/env"
else
echo "Writing environment configuration file..."
cat >"$AGENT_DIR/env" <<EOF
LISTEN=$PORT
KEY="$KEY"
TOKEN=$TOKEN
HUB_URL=$HUB_URL
EOF
else
echo "FreeBSD environment file already exists. Skipping creation."
fi
chmod 640 "$AGENT_DIR/env"
chown "root:${AGENT_USER}" "$AGENT_DIR/env"
@@ -1074,7 +1148,15 @@ $(if [ -n "$NVIDIA_DEVICES" ]; then printf "%b" "# NVIDIA device permissions\n${
WantedBy=multi-user.target
EOF
else
echo "Systemd service file already exists. Skipping creation."
echo "Systemd service file already exists. Updating environment variables..."
SED_PORT=$(escape_sed_replacement "$PORT")
SED_KEY=$(escape_sed_replacement "$KEY")
SED_TOKEN=$(escape_sed_replacement "$TOKEN")
SED_HUB_URL=$(escape_sed_replacement "$HUB_URL")
[ "$PORT_PROVIDED" = "true" ] && sed -i "s|^Environment=\"PORT=.*\"|Environment=\"PORT=$SED_PORT\"|" /etc/systemd/system/beszel-agent.service
[ "$KEY_PROVIDED" = "true" ] && sed -i "s|^Environment=\"KEY=.*\"|Environment=\"KEY=$SED_KEY\"|" /etc/systemd/system/beszel-agent.service
[ "$TOKEN_PROVIDED" = "true" ] && sed -i "s|^Environment=\"TOKEN=.*\"|Environment=\"TOKEN=$SED_TOKEN\"|" /etc/systemd/system/beszel-agent.service
[ "$HUB_URL_PROVIDED" = "true" ] && sed -i "s|^Environment=\"HUB_URL=.*\"|Environment=\"HUB_URL=$SED_HUB_URL\"|" /etc/systemd/system/beszel-agent.service
fi
# Load and start the service
@@ -1140,4 +1222,7 @@ EOF
fi
fi
printf "\n\033[32mBeszel Agent has been installed successfully! It is now running on $PORT.\033[0m\n"
RUNNING_ADDRESS=$(configured_address)
[ -n "$RUNNING_ADDRESS" ] || RUNNING_ADDRESS=$PORT
printf "\n\033[32mBeszel Agent has been installed successfully! It is now running on $RUNNING_ADDRESS.\033[0m\n"