From 4356ffbe9bbe97a4cf31fe09944df64e4d134a55 Mon Sep 17 00:00:00 2001 From: henrygd Date: Tue, 8 Jul 2025 15:33:26 -0400 Subject: [PATCH] add install scripts for beta versions --- supplemental/scripts/install-agent-beta.ps1 | 605 ++++++++++++++ supplemental/scripts/install-agent-beta.sh | 757 ++++++++++++++++++ .../scripts/install-agent-brew-beta.sh | 104 +++ 3 files changed, 1466 insertions(+) create mode 100644 supplemental/scripts/install-agent-beta.ps1 create mode 100755 supplemental/scripts/install-agent-beta.sh create mode 100755 supplemental/scripts/install-agent-brew-beta.sh diff --git a/supplemental/scripts/install-agent-beta.ps1 b/supplemental/scripts/install-agent-beta.ps1 new file mode 100644 index 0000000..1a4d8cb --- /dev/null +++ b/supplemental/scripts/install-agent-beta.ps1 @@ -0,0 +1,605 @@ +param ( + [switch]$Elevated, + [Parameter(Mandatory=$true)] + [string]$Key, + [Parameter(Mandatory=$true)] + [string]$Token, + [Parameter(Mandatory=$true)] + [string]$Url, + [int]$Port = 45876, + [string]$AgentPath = "", + [string]$NSSMPath = "" +) + +# Check if required parameters are provided +if ([string]::IsNullOrWhiteSpace($Key)) { + Write-Host "ERROR: SSH Key is required." -ForegroundColor Red + Write-Host "Usage: .\install-agent.ps1 -Key 'your-ssh-key-here' -Token 'your-token-here' -Url 'your-hub-url-here' [-Port port-number]" -ForegroundColor Yellow + exit 1 +} + +if ([string]::IsNullOrWhiteSpace($Token)) { + Write-Host "ERROR: Token is required." -ForegroundColor Red + Write-Host "Usage: .\install-agent.ps1 -Key 'your-ssh-key-here' -Token 'your-token-here' -Url 'your-hub-url-here' [-Port port-number]" -ForegroundColor Yellow + exit 1 +} + +if ([string]::IsNullOrWhiteSpace($Url)) { + Write-Host "ERROR: Hub URL is required." -ForegroundColor Red + Write-Host "Usage: .\install-agent.ps1 -Key 'your-ssh-key-here' -Token 'your-token-here' -Url 'your-hub-url-here' [-Port port-number]" -ForegroundColor Yellow + exit 1 +} + +# Stop on first error +$ErrorActionPreference = "Stop" + +#region Utility Functions + +# Function to check if running as admin +function Test-Admin { + return ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) +} + +# Function to check if a command exists +function Test-CommandExists { + param ( + [Parameter(Mandatory=$true)] + [string]$Command + ) + return (Get-Command $Command -ErrorAction SilentlyContinue) +} + +# Function to find beszel-agent in common installation locations +function Find-BeszelAgent { + # First check if it's in PATH + $agentCmd = Get-Command "beszel-agent" -ErrorAction SilentlyContinue + if ($agentCmd) { + return $agentCmd.Source + } + + # Common installation paths to check + $commonPaths = @( + "$env:USERPROFILE\scoop\apps\beszel-agent\current\beszel-agent.exe", + "$env:ProgramData\scoop\apps\beszel-agent\current\beszel-agent.exe", + "$env:LOCALAPPDATA\Microsoft\WinGet\Packages\henrygd.beszel-agent*\beszel-agent.exe", + "$env:ProgramFiles\WinGet\Packages\henrygd.beszel-agent*\beszel-agent.exe", + "${env:ProgramFiles(x86)}\WinGet\Packages\henrygd.beszel-agent*\beszel-agent.exe", + "$env:ProgramFiles\beszel-agent\beszel-agent.exe", + "$env:ProgramFiles(x86)\beszel-agent\beszel-agent.exe", + "$env:SystemDrive\Users\*\scoop\apps\beszel-agent\current\beszel-agent.exe" + ) + + foreach ($path in $commonPaths) { + # Handle wildcard paths + if ($path.Contains("*")) { + $foundPaths = Get-ChildItem -Path $path -ErrorAction SilentlyContinue + if ($foundPaths) { + return $foundPaths[0].FullName + } + } else { + if (Test-Path $path) { + return $path + } + } + } + + return $null +} + +# Function to find NSSM in common installation locations +function Find-NSSM { + # First check if it's in PATH + $nssmCmd = Get-Command "nssm" -ErrorAction SilentlyContinue + if ($nssmCmd) { + return $nssmCmd.Source + } + + # Common installation paths to check + $commonPaths = @( + "$env:USERPROFILE\scoop\apps\nssm\current\nssm.exe", + "$env:ProgramData\scoop\apps\nssm\current\nssm.exe", + "$env:LOCALAPPDATA\Microsoft\WinGet\Packages\NSSM.NSSM*\nssm.exe", + "$env:ProgramFiles\WinGet\Packages\NSSM.NSSM*\nssm.exe", + "${env:ProgramFiles(x86)}\WinGet\Packages\NSSM.NSSM*\nssm.exe", + "$env:SystemDrive\Users\*\scoop\apps\nssm\current\nssm.exe" + ) + + foreach ($path in $commonPaths) { + # Handle wildcard paths + if ($path.Contains("*")) { + $foundPaths = Get-ChildItem -Path $path -ErrorAction SilentlyContinue + if ($foundPaths) { + return $foundPaths[0].FullName + } + } else { + if (Test-Path $path) { + return $path + } + } + } + + return $null +} + +#endregion + +#region Installation Methods + +# Function to install Scoop +function Install-Scoop { + Write-Host "Installing Scoop..." + + # Check if running as admin - Scoop should not be installed as admin + if (Test-Admin) { + throw "Scoop cannot be installed with administrator privileges. Please run this script as a regular user first to install Scoop and beszel-agent, then run as admin to configure the service." + } + + try { + Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression + + if (-not (Test-CommandExists "scoop")) { + throw "Failed to install Scoop - command not available after installation" + } + Write-Host "Scoop installed successfully." + } + catch { + throw "Failed to install Scoop: $($_.Exception.Message)" + } +} + +# Function to install Git via Scoop +function Install-Git { + if (Test-CommandExists "git") { + Write-Host "Git is already installed." + return + } + + Write-Host "Installing Git..." + scoop install git + + if (-not (Test-CommandExists "git")) { + throw "Failed to install Git" + } +} + +# Function to install NSSM +function Install-NSSM { + param ( + [string]$Method = "Scoop" # Default to Scoop method + ) + + if (Test-CommandExists "nssm") { + Write-Host "NSSM is already installed." + return + } + + Write-Host "Installing NSSM..." + if ($Method -eq "Scoop") { + scoop install nssm + } + elseif ($Method -eq "WinGet") { + winget install -e --id NSSM.NSSM --accept-source-agreements --accept-package-agreements + + # Refresh PATH environment variable to make NSSM available in current session + $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User") + } + else { + throw "Unsupported installation method: $Method" + } + + if (-not (Test-CommandExists "nssm")) { + throw "Failed to install NSSM" + } +} + +# Function to install beszel-agent with Scoop +function Install-BeszelAgentWithScoop { + Write-Host "Adding beszel bucket..." + scoop bucket add beszel https://github.com/henrygd/beszel-scoops | Out-Null + + Write-Host "Installing / updating beszel-agent..." + scoop install beszel-agent + + if (-not (Test-CommandExists "beszel-agent")) { + throw "Failed to install beszel-agent" + } + + return $(Join-Path -Path $(scoop prefix beszel-agent) -ChildPath "beszel-agent.exe") +} + +# Function to install beszel-agent with WinGet +function Install-BeszelAgentWithWinGet { + Write-Host "Installing / updating beszel-agent..." + + # Temporarily change ErrorActionPreference to allow WinGet to complete and show output + $originalErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = "Continue" + + # Use call operator (&) and capture exit code properly + & winget install --exact --id henrygd.beszel-agent --accept-source-agreements --accept-package-agreements | Out-Null + $wingetExitCode = $LASTEXITCODE + + # Restore original ErrorActionPreference + $ErrorActionPreference = $originalErrorActionPreference + + # WinGet exit codes: + # 0 = Success + # -1978335212 (0x8A150014) = No applicable upgrade found (package is up to date) + # -1978335189 (0x8A15002B) = Another "no upgrade needed" variant + # Other codes indicate actual errors + if ($wingetExitCode -eq -1978335212 -or $wingetExitCode -eq -1978335189) { + Write-Host "Package is already up to date." -ForegroundColor Green + } elseif ($wingetExitCode -ne 0) { + Write-Host "WinGet exit code: $wingetExitCode" -ForegroundColor Yellow + } + + # Refresh PATH environment variable to make beszel-agent available in current session + $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User") + + # Find the path to the beszel-agent executable + $agentPath = (Get-Command beszel-agent -ErrorAction SilentlyContinue).Source + + if (-not $agentPath) { + throw "Could not find beszel-agent executable path after installation" + } + + return $agentPath +} + +# Function to install using Scoop +function Install-WithScoop { + param ( + [string]$Key, + [int]$Port + ) + + try { + # Ensure Scoop is installed + if (-not (Test-CommandExists "scoop")) { + Install-Scoop | Out-Null + } + else { + Write-Host "Scoop is already installed." + } + + # Install Git (required for Scoop buckets) + Install-Git | Out-Null + + # Install NSSM + Install-NSSM -Method "Scoop" | Out-Null + + # Install beszel-agent + $agentPath = Install-BeszelAgentWithScoop + + return $agentPath + } + catch { + Write-Host "ERROR: $($_.Exception.Message)" -ForegroundColor Red + Write-Host "Installation failed. Please check the error message above." -ForegroundColor Red + Write-Host "Press any key to exit..." -ForegroundColor Red + $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") + exit 1 + } +} + +# Function to install using WinGet +function Install-WithWinGet { + param ( + [string]$Key, + [int]$Port + ) + + try { + # Install NSSM + Install-NSSM -Method "WinGet" | Out-Null + + # Install beszel-agent + $agentPath = Install-BeszelAgentWithWinGet + + return $agentPath + } + catch { + Write-Host "ERROR: $($_.Exception.Message)" -ForegroundColor Red + Write-Host "Installation failed. Please check the error message above." -ForegroundColor Red + Write-Host "Press any key to exit..." -ForegroundColor Red + $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") + exit 1 + } +} + +#endregion + +#region Service Configuration + +# Function to install and configure the NSSM service +function Install-NSSMService { + param ( + [Parameter(Mandatory=$true)] + [string]$AgentPath, + [Parameter(Mandatory=$true)] + [string]$Key, + [Parameter(Mandatory=$true)] + [string]$Token, + [Parameter(Mandatory=$true)] + [string]$HubUrl, + [Parameter(Mandatory=$true)] + [int]$Port, + [string]$NSSMPath = "" + ) + + Write-Host "Installing beszel-agent service..." + + # Determine the NSSM executable to use + $nssmCommand = "nssm" + if ($NSSMPath -and (Test-Path $NSSMPath)) { + $nssmCommand = $NSSMPath + Write-Host "Using NSSM from: $NSSMPath" + } elseif (-not (Test-CommandExists "nssm")) { + throw "NSSM is not available in PATH and no valid NSSMPath was provided" + } + + # Check if service already exists + $existingService = Get-Service -Name "beszel-agent" -ErrorAction SilentlyContinue + if ($existingService) { + Write-Host "Service already exists. Stopping and removing existing service..." + try { + & $nssmCommand stop beszel-agent + & $nssmCommand remove beszel-agent confirm + } catch { + Write-Host "Warning: Failed to remove existing service: $($_.Exception.Message)" -ForegroundColor Yellow + } + } + + & $nssmCommand install beszel-agent $AgentPath + if ($LASTEXITCODE -ne 0) { + throw "Failed to install beszel-agent service" + } + + Write-Host "Configuring service environment variables..." + & $nssmCommand set beszel-agent AppEnvironmentExtra "+KEY=$Key" + & $nssmCommand set beszel-agent AppEnvironmentExtra "+TOKEN=$Token" + & $nssmCommand set beszel-agent AppEnvironmentExtra "+HUB_URL=$HubUrl" + & $nssmCommand set beszel-agent AppEnvironmentExtra "+PORT=$Port" + + # Configure log files + $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 +} + +# Function to configure firewall rules +function Configure-Firewall { + param ( + [Parameter(Mandatory=$true)] + [int]$Port + ) + + # Create a firewall rule if it doesn't exist + $ruleName = "Allow beszel-agent" + $existingRule = Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue + + # Remove existing rule if found + if ($existingRule) { + Write-Host "Removing existing firewall rule..." + try { + Remove-NetFirewallRule -DisplayName $ruleName + Write-Host "Existing firewall rule removed successfully." + } catch { + Write-Host "Warning: Failed to remove existing firewall rule: $($_.Exception.Message)" -ForegroundColor Yellow + } + } + + # Create new rule with current settings + Write-Host "Creating firewall rule for beszel-agent on port $Port..." + try { + New-NetFirewallRule -DisplayName $ruleName -Direction Inbound -Action Allow -Protocol TCP -LocalPort $Port + Write-Host "Firewall rule created successfully." + } catch { + Write-Host "Warning: Failed to create firewall rule: $($_.Exception.Message)" -ForegroundColor Yellow + Write-Host "You may need to manually create a firewall rule for port $Port." -ForegroundColor Yellow + } +} + +# Function to start and monitor the service +function Start-BeszelAgentService { + param ( + [string]$NSSMPath = "" + ) + + Write-Host "Starting beszel-agent service..." + + # Determine the NSSM executable to use + $nssmCommand = "nssm" + if ($NSSMPath -and (Test-Path $NSSMPath)) { + $nssmCommand = $NSSMPath + } elseif (-not (Test-CommandExists "nssm")) { + throw "NSSM is not available in PATH and no valid NSSMPath was provided" + } + + & $nssmCommand start beszel-agent + $startResult = $LASTEXITCODE + + # Only enter the status check loop if the NSSM start command failed + if ($startResult -ne 0) { + Write-Host "NSSM start command returned error code: $startResult" -ForegroundColor Yellow + Write-Host "This could be due to 'SERVICE_START_PENDING' state. Checking service status..." + + # Allow up to 10 seconds for the service to start, checking every second + $maxWaitTime = 10 # seconds + $elapsedTime = 0 + $serviceStarted = $false + + while (-not $serviceStarted -and $elapsedTime -lt $maxWaitTime) { + Start-Sleep -Seconds 1 + $elapsedTime += 1 + + $serviceStatus = & $nssmCommand status beszel-agent + + if ($serviceStatus -eq "SERVICE_RUNNING") { + $serviceStarted = $true + Write-Host "Success! The beszel-agent service is now running." -ForegroundColor Green + } + elseif ($serviceStatus -like "*PENDING*") { + Write-Host "Service is still starting (status: $serviceStatus)... waiting" -ForegroundColor Yellow + } + else { + Write-Host "Warning: The service status is '$serviceStatus' instead of 'SERVICE_RUNNING'." -ForegroundColor Yellow + Write-Host "You may need to troubleshoot the service installation." -ForegroundColor Yellow + break + } + } + + if (-not $serviceStarted) { + Write-Host "Service did not reach running state." -ForegroundColor Yellow + Write-Host "You can check status manually with 'nssm status beszel-agent'" -ForegroundColor Yellow + } + } else { + # NSSM start command was successful + Write-Host "Success! The beszel-agent service is running properly." -ForegroundColor Green + } +} + +#endregion + +#region Main Script Execution + +# Check if we're running as admin +$isAdmin = Test-Admin + +try { + # First: Install the agent (doesn't require admin) + if (-not $AgentPath) { + # Check for problematic case: running as admin and need Scoop + if ($isAdmin -and -not (Test-CommandExists "scoop") -and -not (Test-CommandExists "winget")) { + Write-Host "ERROR: You're running as administrator but neither Scoop nor WinGet is available." -ForegroundColor Red + Write-Host "Scoop should be installed without admin privileges." -ForegroundColor Red + Write-Host "" + Write-Host "Please either:" -ForegroundColor Yellow + Write-Host "1. Run this script again without administrator privileges" -ForegroundColor Yellow + Write-Host "2. Install WinGet and run this script again" -ForegroundColor Yellow + exit 1 + } + + if (Test-CommandExists "scoop") { + Write-Host "Using Scoop for installation..." + $AgentPath = Install-WithScoop -Key $Key -Port $Port + } + elseif (Test-CommandExists "winget") { + Write-Host "Using WinGet for installation..." + $AgentPath = Install-WithWinGet -Key $Key -Port $Port + } + else { + Write-Host "Neither Scoop nor WinGet is installed. Installing Scoop..." + $AgentPath = Install-WithScoop -Key $Key -Port $Port + } + } + + if (-not $AgentPath) { + throw "Could not find beszel-agent executable. Make sure it was properly installed." + } + + # Find NSSM path if not already provided + if (-not $NSSMPath) { + $NSSMPath = Find-NSSM + + if (-not $NSSMPath -and (Test-CommandExists "nssm")) { + $NSSMPath = (Get-Command "nssm" -ErrorAction SilentlyContinue).Source + } + + # If we still don't have NSSM, try to install it if we have package managers + if (-not $NSSMPath) { + if (Test-CommandExists "winget") { + Write-Host "NSSM not found. Attempting to install via WinGet..." + try { + Install-NSSM -Method "WinGet" + $NSSMPath = Find-NSSM + if (-not $NSSMPath -and (Test-CommandExists "nssm")) { + $NSSMPath = (Get-Command "nssm" -ErrorAction SilentlyContinue).Source + } + } catch { + Write-Host "Failed to install NSSM via WinGet: $($_.Exception.Message)" -ForegroundColor Yellow + } + } elseif (Test-CommandExists "scoop") { + Write-Host "NSSM not found. Attempting to install via Scoop..." + try { + Install-NSSM -Method "Scoop" + $NSSMPath = Find-NSSM + if (-not $NSSMPath -and (Test-CommandExists "nssm")) { + $NSSMPath = (Get-Command "nssm" -ErrorAction SilentlyContinue).Source + } + } catch { + Write-Host "Failed to install NSSM via Scoop: $($_.Exception.Message)" -ForegroundColor Yellow + } + } + + # Final check - if we still don't have NSSM and we're admin, we have a problem + if (-not $NSSMPath -and ($isAdmin -or $Elevated)) { + throw "NSSM is required for service installation but was not found and could not be installed. Please install NSSM manually or run as a regular user to install it." + } + } + } + + # Second: If we need admin rights for service installation and we don't have them, relaunch + if (-not $isAdmin -and -not $Elevated) { + Write-Host "Admin privileges required for service installation. Relaunching as admin..." -ForegroundColor Yellow + Write-Host "Check service status with 'nssm status beszel-agent'" + Write-Host "Edit service configuration with 'nssm edit beszel-agent'" + + # Prepare arguments for the elevated script + $argumentList = @( + "-ExecutionPolicy", "Bypass", + "-File", "`"$PSCommandPath`"", + "-Elevated", + "-Key", "`"$Key`"", + "-Token", "`"$Token`"", + "-Url", "`"$Url`"", + "-Port", $Port, + "-AgentPath", "`"$AgentPath`"" + ) + + # Add NSSMPath if we found it + if ($NSSMPath) { + $argumentList += "-NSSMPath" + $argumentList += "`"$NSSMPath`"" + } + + # Relaunch the script with the -Elevated switch and pass parameters + Start-Process powershell.exe -Verb RunAs -ArgumentList $argumentList + exit + } + + # 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 + + # Configure firewall + Configure-Firewall -Port $Port + + # Start the service + Start-BeszelAgentService -NSSMPath $NSSMPath + + # Pause to see results if this is an elevated window + if ($Elevated) { + Write-Host "Press any key to exit..." -ForegroundColor Cyan + $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") + } + } +} +catch { + Write-Host "ERROR: $($_.Exception.Message)" -ForegroundColor Red + Write-Host "Installation failed. Please check the error message above." -ForegroundColor Red + + # Pause if this is likely a new window + if ($Elevated -or (-not $isAdmin)) { + Write-Host "Press any key to exit..." -ForegroundColor Red + $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") + } + exit 1 +} + +#endregion diff --git a/supplemental/scripts/install-agent-beta.sh b/supplemental/scripts/install-agent-beta.sh new file mode 100755 index 0000000..349eaf7 --- /dev/null +++ b/supplemental/scripts/install-agent-beta.sh @@ -0,0 +1,757 @@ +#!/bin/sh + +is_alpine() { + [ -f /etc/alpine-release ] +} + +is_openwrt() { + cat /etc/os-release | grep -q "OpenWrt" +} + +# If SELinux is enabled, set the context of the binary +set_selinux_context() { + # Check if SELinux is enabled and in enforcing or permissive mode + if command -v getenforce >/dev/null 2>&1; then + SELINUX_MODE=$(getenforce) + if [ "$SELINUX_MODE" != "Disabled" ]; then + echo "SELinux is enabled (${SELINUX_MODE} mode). Setting appropriate context..." + + # First try to set persistent context if semanage is available + if command -v semanage >/dev/null 2>&1; then + echo "Attempting to set persistent SELinux context..." + if semanage fcontext -a -t bin_t "/opt/beszel-agent/beszel-agent" >/dev/null 2>&1; then + restorecon -v /opt/beszel-agent/beszel-agent >/dev/null 2>&1 + else + echo "Warning: Failed to set persistent context, falling back to temporary context." + fi + fi + + # Fall back to chcon if semanage failed or isn't available + if command -v chcon >/dev/null 2>&1; then + # Set context for both the directory and binary + chcon -t bin_t /opt/beszel-agent/beszel-agent || echo "Warning: Failed to set SELinux context for binary." + chcon -R -t bin_t /opt/beszel-agent || echo "Warning: Failed to set SELinux context for directory." + else + if [ "$SELINUX_MODE" = "Enforcing" ]; then + echo "Warning: SELinux is in enforcing mode but chcon command not found. The service may fail to start." + echo "Consider installing the policycoreutils package or temporarily setting SELinux to permissive mode." + else + echo "Warning: SELinux is in permissive mode but chcon command not found." + fi + fi + fi + fi +} + +# Clean up SELinux contexts if they were set +cleanup_selinux_context() { + if command -v getenforce >/dev/null 2>&1 && [ "$(getenforce)" != "Disabled" ]; then + echo "Cleaning up SELinux contexts..." + # Remove persistent context if semanage is available + if command -v semanage >/dev/null 2>&1; then + semanage fcontext -d "/opt/beszel-agent/beszel-agent" 2>/dev/null || true + fi + fi +} + +# Ensure the proxy URL ends with a / +ensure_trailing_slash() { + if [ -n "$1" ]; then + case "$1" in + */) echo "$1" ;; + *) echo "$1/" ;; + esac + else + echo "$1" + fi +} + +# Default values +PORT=45876 +UNINSTALL=false +GITHUB_URL="https://github.com" +GITHUB_API_URL="https://api.github.com" # not blocked in China currently +GITHUB_PROXY_URL="" +KEY="" +TOKEN="" +HUB_URL="" +AUTO_UPDATE_FLAG="" # empty string means prompt, "true" means auto-enable, "false" means skip +VERSION="latest" + +# Check for help flag +case "$1" in +-h | --help) + printf "Beszel Agent installation script\n\n" + printf "Usage: ./install-agent.sh [options]\n\n" + printf "Options: \n" + printf " -k : SSH key (required, or interactive if not provided)\n" + printf " -p : Port (default: $PORT)\n" + printf " -t : Token (required, or interactive if not provided)\n" + printf " -url : Hub URL (required, or interactive if not provided)\n" + printf " -v, --version : Version to install (default: latest)\n" + printf " -u : Uninstall Beszel Agent\n" + printf " --auto-update [VALUE] : Control automatic daily updates\n" + printf " VALUE can be true (enable) or false (disable). If not specified, will prompt.\n" + printf " --china-mirrors [URL] : Use GitHub proxy to resolve network timeout issues in mainland China\n" + printf " URL: optional custom proxy URL (default: https://gh.beszel.dev)\n" + printf " -h, --help : Display this help message\n" + exit 0 + ;; +esac + +# Build sudo args by properly quoting everything +build_sudo_args() { + QUOTED_ARGS="" + while [ $# -gt 0 ]; do + if [ -n "$QUOTED_ARGS" ]; then + QUOTED_ARGS="$QUOTED_ARGS " + fi + QUOTED_ARGS="$QUOTED_ARGS'$(echo "$1" | sed "s/'/'\\\\''/g")'" + shift + done + echo "$QUOTED_ARGS" +} + +# Check if running as root and re-execute with sudo if needed +if [ "$(id -u)" != "0" ]; then + if command -v sudo >/dev/null 2>&1; then + SUDO_ARGS=$(build_sudo_args "$@") + eval "exec sudo $0 $SUDO_ARGS" + else + echo "This script must be run as root. Please either:" + echo "1. Run this script as root (su root)" + echo "2. Install sudo and run with sudo" + exit 1 + fi +fi + +# Parse arguments +while [ $# -gt 0 ]; do + case "$1" in + -k) + shift + KEY="$1" + ;; + -p) + shift + PORT="$1" + ;; + -t) + shift + TOKEN="$1" + ;; + -url) + shift + HUB_URL="$1" + ;; + -v|--version) + shift + VERSION="$1" + ;; + -u) + UNINSTALL=true + ;; + --china-mirrors*) + # Check if there's a value after the = sign + if echo "$1" | grep -q "="; then + # Extract the value after = + CUSTOM_PROXY=$(echo "$1" | cut -d'=' -f2) + if [ -n "$CUSTOM_PROXY" ]; then + GITHUB_PROXY_URL="$CUSTOM_PROXY" + GITHUB_URL="$(ensure_trailing_slash "$CUSTOM_PROXY")https://github.com" + else + GITHUB_PROXY_URL="https://gh.beszel.dev" + GITHUB_URL="$GITHUB_PROXY_URL" + fi + elif [ "$2" != "" ] && ! echo "$2" | grep -q '^-'; then + # use custom proxy URL provided as next argument + GITHUB_PROXY_URL="$2" + GITHUB_URL="$(ensure_trailing_slash "$2")https://github.com" + shift + else + # No value specified, use default + GITHUB_PROXY_URL="https://gh.beszel.dev" + GITHUB_URL="$GITHUB_PROXY_URL" + fi + ;; + --auto-update*) + # Check if there's a value after the = sign + if echo "$1" | grep -q "="; then + # Extract the value after = + AUTO_UPDATE_VALUE=$(echo "$1" | cut -d'=' -f2) + if [ "$AUTO_UPDATE_VALUE" = "true" ]; then + AUTO_UPDATE_FLAG="true" + elif [ "$AUTO_UPDATE_VALUE" = "false" ]; then + AUTO_UPDATE_FLAG="false" + else + echo "Invalid value for --auto-update flag: $AUTO_UPDATE_VALUE. Using default (prompt)." + fi + elif [ "$2" = "true" ] || [ "$2" = "false" ]; then + # Value provided as next argument + AUTO_UPDATE_FLAG="$2" + shift + else + # No value specified, use true + AUTO_UPDATE_FLAG="true" + fi + ;; + *) + echo "Invalid option: $1" >&2 + exit 1 + ;; + esac + shift +done + +# Uninstall process +if [ "$UNINSTALL" = true ]; then + # Clean up SELinux contexts before removing files + cleanup_selinux_context + + if is_alpine; then + echo "Stopping and disabling the agent service..." + rc-service beszel-agent stop + rc-update del beszel-agent default + + echo "Removing the OpenRC service files..." + rm -f /etc/init.d/beszel-agent + + # Remove the update service if it exists + echo "Removing the daily update service..." + rc-service beszel-agent-update stop 2>/dev/null + rc-update del beszel-agent-update default 2>/dev/null + rm -f /etc/init.d/beszel-agent-update + + # Remove log files + echo "Removing log files..." + rm -f /var/log/beszel-agent.log /var/log/beszel-agent.err + elif is_openwrt; then + echo "Stopping and disabling the agent service..." + service beszel-agent stop + service beszel-agent disable + + echo "Removing the OpenWRT service files..." + rm -f /etc/init.d/beszel-agent + + # Remove the update service if it exists + echo "Removing the daily update service..." + rm -f /etc/crontabs/beszel + + else + echo "Stopping and disabling the agent service..." + systemctl stop beszel-agent.service + systemctl disable beszel-agent.service + + echo "Removing the systemd service file..." + rm /etc/systemd/system/beszel-agent.service + + # Remove the update timer and service if they exist + echo "Removing the daily update service and timer..." + systemctl stop beszel-agent-update.timer 2>/dev/null + systemctl disable beszel-agent-update.timer 2>/dev/null + rm -f /etc/systemd/system/beszel-agent-update.service + rm -f /etc/systemd/system/beszel-agent-update.timer + + systemctl daemon-reload + fi + + echo "Removing the Beszel Agent directory..." + rm -rf /opt/beszel-agent + + echo "Removing the dedicated user for the agent service..." + killall beszel-agent 2>/dev/null + if is_alpine || is_openwrt; then + deluser beszel 2>/dev/null + else + userdel beszel 2>/dev/null + fi + + echo "Beszel Agent has been uninstalled successfully!" + exit 0 +fi + +# Confirm the use of GitHub mirrors for downloads +if [ -n "$GITHUB_PROXY_URL" ]; then + printf "\nConfirm use of GitHub mirror (%s) for downloading beszel-agent?\nThis helps to install properly in mainland China. (Y/n): " "$GITHUB_PROXY_URL" + read USE_MIRROR + USE_MIRROR=${USE_MIRROR:-Y} + if [ "$USE_MIRROR" = "Y" ] || [ "$USE_MIRROR" = "y" ]; then + echo "Using GitHub Mirror ($GITHUB_PROXY_URL) for downloads..." + else + GITHUB_URL="https://github.com" + fi +fi + +# Check if a package is installed +package_installed() { + command -v "$1" >/dev/null 2>&1 +} + +# Check for package manager and install necessary packages if not installed +if is_alpine; then + if ! package_installed tar || ! package_installed curl || ! package_installed coreutils; then + apk update + apk add tar curl coreutils shadow + fi +elif is_openwrt; then + if ! package_installed tar || ! package_installed curl || ! package_installed coreutils; then + opkg update + opkg install tar curl coreutils + fi +elif package_installed apt-get; then + if ! package_installed tar || ! package_installed curl || ! package_installed sha256sum; then + apt-get update + apt-get install -y tar curl coreutils + fi +elif package_installed yum; then + if ! package_installed tar || ! package_installed curl || ! package_installed sha256sum; then + yum install -y tar curl coreutils + fi +elif package_installed pacman; then + if ! package_installed tar || ! package_installed curl || ! package_installed sha256sum; then + pacman -Sy --noconfirm tar curl coreutils + fi +else + echo "Warning: Please ensure 'tar' and 'curl' and 'sha256sum (coreutils)' are installed." +fi + +# If no SSH key is provided, ask for the SSH key interactively +if [ -z "$KEY" ]; then + printf "Enter your SSH key: " + read KEY +fi + +# If no token is provided, ask for the token interactively +if [ -z "$TOKEN" ]; then + printf "Enter your token: " + read TOKEN +fi + +# If no hub URL is provided, ask for the hub URL interactively +if [ -z "$HUB_URL" ]; then + printf "Enter your hub URL: " + read HUB_URL +fi + +# Verify checksum +if command -v sha256sum >/dev/null; then + CHECK_CMD="sha256sum" +elif command -v md5 >/dev/null; then + CHECK_CMD="md5 -q" +else + echo "No MD5 checksum utility found" + exit 1 +fi + +# Create a dedicated user for the service if it doesn't exist +if is_alpine; then + if ! id -u beszel >/dev/null 2>&1; then + echo "Creating a dedicated group for the Beszel Agent service..." + addgroup beszel + echo "Creating a dedicated user for the Beszel Agent service..." + adduser -S -D -H -s /sbin/nologin -G beszel beszel + fi + # Add the user to the docker group to allow access to the Docker socket if group docker exists + if getent group docker; then + echo "Adding besel to docker group" + usermod -aG docker beszel + fi + +else + if ! id -u beszel >/dev/null 2>&1; then + echo "Creating a dedicated user for the Beszel Agent service..." + useradd --system --home-dir /nonexistent --shell /bin/false beszel + fi +# Add the user to the docker group to allow access to the Docker socket if group docker exists + if getent group docker; then + echo "Adding besel to docker group" + usermod -aG docker beszel + fi +fi + +# Create the directory for the Beszel Agent +if [ ! -d "/opt/beszel-agent" ]; then + echo "Creating the directory for the Beszel Agent..." + mkdir -p /opt/beszel-agent + chown beszel:beszel /opt/beszel-agent + chmod 755 /opt/beszel-agent +fi + +# Download and install the Beszel Agent +echo "Downloading and installing the agent..." + +OS=$(uname -s | sed -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/') +ARCH=$(uname -m | sed -e 's/x86_64/amd64/' -e 's/armv6l/arm/' -e 's/armv7l/arm/' -e 's/aarch64/arm64/' -e 's/mips/mipsle/') +FILE_NAME="beszel-agent_${OS}_${ARCH}.tar.gz" + +# Determine version to install +if [ "$VERSION" = "latest" ]; then + INSTALL_VERSION=$(curl -s "$GITHUB_API_URL""/repos/henrygd/beszel/releases/latest" | grep -o '"tag_name": "v[^"]*"' | cut -d'"' -f4 | tr -d 'v') + if [ -z "$INSTALL_VERSION" ]; then + echo "Failed to get latest version" + exit 1 + fi +else + INSTALL_VERSION="$VERSION" + # Remove 'v' prefix if present + INSTALL_VERSION=$(echo "$INSTALL_VERSION" | sed 's/^v//') +fi + +echo "Downloading and installing agent version ${INSTALL_VERSION} from ${GITHUB_URL} ..." + +# Download checksums file +TEMP_DIR=$(mktemp -d) +cd "$TEMP_DIR" || exit 1 +CHECKSUM=$(curl -sL "$GITHUB_URL/henrygd/beszel/releases/download/v${INSTALL_VERSION}/beszel_${INSTALL_VERSION}_checksums.txt" | grep "$FILE_NAME" | cut -d' ' -f1) +if [ -z "$CHECKSUM" ] || ! echo "$CHECKSUM" | grep -qE "^[a-fA-F0-9]{64}$"; then + echo "Failed to get checksum or invalid checksum format" + exit 1 +fi + +if ! curl -#L "$GITHUB_URL/henrygd/beszel/releases/download/v${INSTALL_VERSION}/$FILE_NAME" -o "$FILE_NAME"; then + echo "Failed to download the agent from ""$GITHUB_URL/henrygd/beszel/releases/download/v${INSTALL_VERSION}/$FILE_NAME" + rm -rf "$TEMP_DIR" + exit 1 +fi + +if [ "$($CHECK_CMD "$FILE_NAME" | cut -d' ' -f1)" != "$CHECKSUM" ]; then + echo "Checksum verification failed: $($CHECK_CMD "$FILE_NAME" | cut -d' ' -f1) & $CHECKSUM" + rm -rf "$TEMP_DIR" + exit 1 +fi + +if ! tar -xzf "$FILE_NAME" beszel-agent; then + echo "Failed to extract the agent" + rm -rf "$TEMP_DIR" + exit 1 +fi + +mv beszel-agent /opt/beszel-agent/beszel-agent +chown beszel:beszel /opt/beszel-agent/beszel-agent +chmod 755 /opt/beszel-agent/beszel-agent + +# Set SELinux context if needed +set_selinux_context + +# Cleanup +rm -rf "$TEMP_DIR" + +# Check for NVIDIA GPUs and grant device permissions for systemd service +detect_nvidia_devices() { + local devices="" + for i in /dev/nvidia*; do + if [ -e "$i" ]; then + devices="${devices}DeviceAllow=$i rw\n" + fi + done + echo "$devices" +} + +# Modify service installation part, add Alpine check before systemd service creation +if is_alpine; then + echo "Creating OpenRC service for Alpine Linux..." + cat >/etc/init.d/beszel-agent </etc/init.d/beszel-agent-update </dev/null 2>&1; then + echo "Error: The Beszel Agent service is not running." + rc-service beszel-agent status + exit 1 + fi + +elif is_openwrt; then + echo "Creating procd init script service for OpenWRT..." + cat >/etc/init.d/beszel-agent </etc/crontabs/beszel </dev/null 2>&1; then + echo "Error: The Beszel Agent service is not running." + service beszel-agent status + exit 1 + fi + +else + # Original systemd service installation code + echo "Creating the systemd service for the agent..." + + # Detect NVIDIA devices and grant device permissions + NVIDIA_DEVICES=$(detect_nvidia_devices) + + cat >/etc/systemd/system/beszel-agent.service </opt/beszel-agent/run-update.sh <<'EOF' +#!/bin/sh + +set -e + +if /opt/beszel-agent/beszel-agent update | grep -q "Successfully updated"; then + echo "Update found, checking SELinux context." + if command -v getenforce >/dev/null 2>&1 && [ "$(getenforce)" != "Disabled" ]; then + echo "SELinux enabled, applying context..." + if command -v chcon >/dev/null 2>&1; then + chcon -t bin_t /opt/beszel-agent/beszel-agent || echo "Warning: chcon command failed to apply context." + fi + if command -v restorecon >/dev/null 2>&1; then + restorecon -v /opt/beszel-agent/beszel-agent >/dev/null 2>&1 || echo "Warning: restorecon command failed to apply context." + fi + fi + echo "Restarting beszel-agent service..." + systemctl restart beszel-agent + echo "Update process finished." +else + echo "No updates found or applied." +fi + +exit 0 +EOF + + chown root:root /opt/beszel-agent/run-update.sh + chmod +x /opt/beszel-agent/run-update.sh + + # Prompt for auto-update setup + if [ "$AUTO_UPDATE_FLAG" = "true" ]; then + AUTO_UPDATE="y" + sleep 1 # give time for the service to start + elif [ "$AUTO_UPDATE_FLAG" = "false" ]; then + AUTO_UPDATE="n" + sleep 1 # give time for the service to start + else + printf "\nWould you like to enable automatic daily updates for beszel-agent? (y/n): " + read AUTO_UPDATE + fi + case "$AUTO_UPDATE" in + [Yy]*) + echo "Setting up daily automatic updates for beszel-agent..." + + # Create systemd service for the daily update + cat >/etc/systemd/system/beszel-agent-update.service </etc/systemd/system/beszel-agent-update.timer <&2 + usage + ;; + esac + shift +done + +# Check if brew is installed, prompt to install if not +if ! command -v brew &>/dev/null; then + read -p "Homebrew is not installed. Would you like to install it now? (y/n): " install_brew + if [[ $install_brew =~ ^[Yy]$ ]]; then + echo "Installing Homebrew..." + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + + # Verify installation was successful + if ! command -v brew &>/dev/null; then + echo "Homebrew installation failed. Please install manually and try again." + exit 1 + fi + echo "Homebrew installed successfully." + else + echo "Homebrew is required. Please install Homebrew and try again." + exit 1 + fi +fi + +if [ -z "$KEY" ]; then + read -p "Enter SSH key: " KEY +fi + +if [ -z "$TOKEN" ]; then + read -p "Enter token: " TOKEN +fi + +if [ -z "$HUB_URL" ]; then + read -p "Enter hub URL: " HUB_URL +fi + +mkdir -p ~/.config/beszel ~/.cache/beszel + +echo "KEY=\"$KEY\"" >~/.config/beszel/beszel-agent.env +echo "LISTEN=$PORT" >>~/.config/beszel/beszel-agent.env +echo "TOKEN=\"$TOKEN\"" >>~/.config/beszel/beszel-agent.env +echo "HUB_URL=\"$HUB_URL\"" >>~/.config/beszel/beszel-agent.env + +brew tap henrygd/beszel +brew install beszel-agent +brew services start beszel-agent + +printf "\nCheck status: brew services info beszel-agent\n" +echo "Stop: brew services stop beszel-agent" +echo "Start: brew services start beszel-agent" +echo "Restart: brew services restart beszel-agent" +echo "Upgrade: brew upgrade beszel-agent" +echo "Uninstall: brew uninstall beszel-agent" +echo "View logs in ~/.cache/beszel/beszel-agent.log" +printf "Change environment variables in ~/.config/beszel/beszel-agent.env\n"