Compare commits

...

15 Commits

Author SHA1 Message Date
henrygd
3a97edd0d5 add winget support to windows install script 2025-05-01 17:40:20 -04:00
henrygd
ab1d1c1273 Remove PrivateTmp setting from Systemd rules in install-agent.sh
Allows sharing socket in /tmp
2025-05-01 17:00:08 -04:00
henrygd
0fb39edae4 rename ssh imports in server.go 2025-04-30 18:09:25 -04:00
henrygd
3a977a8e1f goreleaser: update deprecated format field 2025-04-30 16:15:37 -04:00
henrygd
081979de24 Add winget configuration for beszel-agent 2025-04-30 15:00:26 -04:00
henrygd
23fe189797 Add SELinux context management to install-agent.sh (#788)
- Introduced functions to set and clean up SELinux contexts.
- Added SELinux context checks during the update process (systemd only).
- Updated the service execution command to use a dedicated update script.
2025-04-29 18:59:36 -04:00
henrygd
e9d429b9b8 Enhance service start check in install-agent.ps1
- Added logic to handle service start failures and check status with retries.
- Improved user feedback for service status during startup process.
2025-04-28 21:47:02 -04:00
henrygd
99202c85b6 re-enable docker image creation workflow 2025-04-28 21:16:32 -04:00
henrygd
d5c3d8f84e release 0.11.1 with new line so goreleaser doesn't fail :)
- Temp disable docker workflow bc image was already build
2025-04-28 20:57:27 -04:00
Alexander Mnich
8f442992e6 New German translations 2025-04-28 20:48:38 -04:00
henrygd
39820c8ac1 release 0.11.1 2025-04-28 20:38:24 -04:00
henrygd
0c8b10af99 Escape backslashes in windows agent install command (#785) 2025-04-28 20:37:25 -04:00
henrygd
8e072492b7 Skip checking Docker if DOCKER_HOST is set to an empty string 2025-04-28 20:23:54 -04:00
henrygd
88d6307ce0 Add FreeBSD icon 2025-04-28 19:37:00 -04:00
henrygd
2cc516f9e5 update readme and notifications link 2025-04-27 20:05:07 -04:00
17 changed files with 469 additions and 122 deletions

View File

@@ -51,7 +51,7 @@ builds:
archives: archives:
- id: beszel-agent - id: beszel-agent
format: tar.gz formats: [tar.gz]
builds: builds:
- beszel-agent - beszel-agent
name_template: >- name_template: >-
@@ -60,10 +60,10 @@ archives:
{{- .Arch }} {{- .Arch }}
format_overrides: format_overrides:
- goos: windows - goos: windows
format: zip formats: [zip]
- id: beszel - id: beszel
format: tar.gz formats: [tar.gz]
builds: builds:
- beszel - beszel
name_template: >- name_template: >-
@@ -87,9 +87,6 @@ nfpms:
- beszel-agent - beszel-agent
formats: formats:
- deb - deb
# don't think this is needed with CGO_ENABLED=0
# dependencies:
# - libc6
contents: contents:
- src: ../supplemental/debian/beszel-agent.service - src: ../supplemental/debian/beszel-agent.service
dst: lib/systemd/system/beszel-agent.service dst: lib/systemd/system/beszel-agent.service
@@ -173,6 +170,41 @@ brews:
error_log_path "#{Dir.home}/.cache/beszel/beszel-agent.log" error_log_path "#{Dir.home}/.cache/beszel/beszel-agent.log"
keep_alive true keep_alive true
winget:
- ids: [beszel-agent]
name: beszel-agent
package_identifier: henrygd.beszel-agent
publisher: henrygd
license: MIT
license_url: 'https://github.com/henrygd/beszel/blob/main/LICENSE'
copyright: '2025 henrygd'
homepage: 'https://beszel.dev'
release_notes_url: 'https://github.com/henrygd/beszel/releases/tag/v{{ .Version }}'
publisher_support_url: 'https://github.com/henrygd/beszel/issues'
short_description: 'Agent for Beszel, a lightweight server monitoring platform.'
skip_upload: auto
description: |
Beszel is a lightweight server monitoring platform that includes Docker
statistics, historical data, and alert functions. It has a friendly web
interface, simple configuration, and is ready to use out of the box.
It supports automatic backup, multi-user, OAuth authentication, and
API access.
tags:
- homelab
- monitoring
- self-hosted
repository:
owner: henrygd
name: beszel-winget
branch: henrygd.beszel-agent-{{ .Version }}
pull_request:
enabled: false
draft: false
base:
owner: microsoft
name: winget-pkgs
branch: master
release: release:
draft: true draft: true

View File

@@ -95,11 +95,13 @@ func (a *Agent) gatherStats(sessionID string) *system.CombinedData {
} }
slog.Debug("System stats", "data", cachedData) slog.Debug("System stats", "data", cachedData)
if containerStats, err := a.dockerManager.getDockerStats(); err == nil { if a.dockerManager != nil {
cachedData.Containers = containerStats if containerStats, err := a.dockerManager.getDockerStats(); err == nil {
slog.Debug("Docker stats", "data", cachedData.Containers) cachedData.Containers = containerStats
} else { slog.Debug("Docker stats", "data", cachedData.Containers)
slog.Debug("Docker stats", "err", err) } else {
slog.Debug("Docker stats", "err", err)
}
} }
cachedData.Stats.ExtraFs = make(map[string]*system.FsStats) cachedData.Stats.ExtraFs = make(map[string]*system.FsStats)

View File

@@ -232,6 +232,10 @@ func newDockerManager(a *Agent) *dockerManager {
dockerHost, exists := GetEnv("DOCKER_HOST") dockerHost, exists := GetEnv("DOCKER_HOST")
if exists { if exists {
slog.Info("DOCKER_HOST", "host", dockerHost) slog.Info("DOCKER_HOST", "host", dockerHost)
// return nil if set to empty string
if dockerHost == "" {
return nil
}
} else { } else {
dockerHost = getDockerHost() dockerHost = getDockerHost()
} }

View File

@@ -8,18 +8,18 @@ import (
"os" "os"
"strings" "strings"
sshServer "github.com/gliderlabs/ssh" "github.com/gliderlabs/ssh"
"golang.org/x/crypto/ssh" gossh "golang.org/x/crypto/ssh"
) )
type ServerOptions struct { type ServerOptions struct {
Addr string Addr string
Network string Network string
Keys []ssh.PublicKey Keys []gossh.PublicKey
} }
func (a *Agent) StartServer(opts ServerOptions) error { func (a *Agent) StartServer(opts ServerOptions) error {
sshServer.Handle(a.handleSession) ssh.Handle(a.handleSession)
slog.Info("Starting SSH server", "addr", opts.Addr, "network", opts.Network) slog.Info("Starting SSH server", "addr", opts.Addr, "network", opts.Network)
@@ -38,10 +38,10 @@ func (a *Agent) StartServer(opts ServerOptions) error {
defer ln.Close() defer ln.Close()
// Start SSH server on the listener // Start SSH server on the listener
return sshServer.Serve(ln, nil, sshServer.NoPty(), return ssh.Serve(ln, nil, ssh.NoPty(),
sshServer.PublicKeyAuth(func(ctx sshServer.Context, key sshServer.PublicKey) bool { ssh.PublicKeyAuth(func(ctx ssh.Context, key ssh.PublicKey) bool {
for _, pubKey := range opts.Keys { for _, pubKey := range opts.Keys {
if sshServer.KeysEqual(key, pubKey) { if ssh.KeysEqual(key, pubKey) {
return true return true
} }
} }
@@ -50,7 +50,7 @@ func (a *Agent) StartServer(opts ServerOptions) error {
) )
} }
func (a *Agent) handleSession(s sshServer.Session) { func (a *Agent) handleSession(s ssh.Session) {
slog.Debug("New session", "client", s.RemoteAddr()) slog.Debug("New session", "client", s.RemoteAddr())
stats := a.gatherStats(s.Context().SessionID()) stats := a.gatherStats(s.Context().SessionID())
if err := json.NewEncoder(s).Encode(stats); err != nil { if err := json.NewEncoder(s).Encode(stats); err != nil {
@@ -62,8 +62,8 @@ func (a *Agent) handleSession(s sshServer.Session) {
// ParseKeys parses a string containing SSH public keys in authorized_keys format. // ParseKeys parses a string containing SSH public keys in authorized_keys format.
// It returns a slice of ssh.PublicKey and an error if any key fails to parse. // It returns a slice of ssh.PublicKey and an error if any key fails to parse.
func ParseKeys(input string) ([]ssh.PublicKey, error) { func ParseKeys(input string) ([]gossh.PublicKey, error) {
var parsedKeys []ssh.PublicKey var parsedKeys []gossh.PublicKey
for line := range strings.Lines(input) { for line := range strings.Lines(input) {
line = strings.TrimSpace(line) line = strings.TrimSpace(line)
// Skip empty lines or comments // Skip empty lines or comments
@@ -71,7 +71,7 @@ func ParseKeys(input string) ([]ssh.PublicKey, error) {
continue continue
} }
// Parse the key // Parse the key
parsedKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(line)) parsedKey, _, _, _, err := gossh.ParseAuthorizedKey([]byte(line))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse key: %s, error: %w", line, err) return nil, fmt.Errorf("failed to parse key: %s, error: %w", line, err)
} }

View File

@@ -31,6 +31,9 @@ func (a *Agent) initializeSystemInfo() {
} else if strings.Contains(platform, "indows") { } else if strings.Contains(platform, "indows") {
a.systemInfo.KernelVersion = strings.Replace(platform, "Microsoft ", "", 1) + " " + version a.systemInfo.KernelVersion = strings.Replace(platform, "Microsoft ", "", 1) + " " + version
a.systemInfo.Os = system.Windows a.systemInfo.Os = system.Windows
} else if platform == "freebsd" {
a.systemInfo.Os = system.Freebsd
a.systemInfo.KernelVersion = version
} else { } else {
a.systemInfo.Os = system.Linux a.systemInfo.Os = system.Linux
} }

View File

@@ -70,6 +70,7 @@ const (
Linux Os = iota Linux Os = iota
Darwin Darwin
Windows Windows
Freebsd
) )
type Info struct { type Info struct {

View File

@@ -1,7 +1,7 @@
{ {
"name": "beszel", "name": "beszel",
"private": true, "private": true,
"version": "0.11.0", "version": "0.11.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -86,7 +86,7 @@ function copyLinuxCommand(port = "45876", publicKey: string, brew = false) {
function copyWindowsCommand(port = "45876", publicKey: string) { function copyWindowsCommand(port = "45876", publicKey: string) {
copyToClipboard( copyToClipboard(
`Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser; & iwr -useb https://get.beszel.dev -OutFile "$env:TEMP\install-agent.ps1"; & "$env:TEMP\install-agent.ps1" -Key "${publicKey}" -Port ${port}` `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser; & iwr -useb https://get.beszel.dev -OutFile "$env:TEMP\\install-agent.ps1"; & "$env:TEMP\\install-agent.ps1" -Key "${publicKey}" -Port ${port}`
) )
} }

View File

@@ -1,5 +1,5 @@
import { t } from "@lingui/core/macro"; import { t } from "@lingui/core/macro"
import { Trans } from "@lingui/react/macro"; import { Trans } from "@lingui/react/macro"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input" import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label" import { Label } from "@/components/ui/label"
@@ -128,7 +128,7 @@ const SettingsNotificationsPage = ({ userSettings }: { userSettings: UserSetting
<p className="text-sm text-muted-foreground leading-relaxed"> <p className="text-sm text-muted-foreground leading-relaxed">
<Trans> <Trans>
Beszel uses{" "} Beszel uses{" "}
<a href="https://containrrr.dev/shoutrrr/services/overview/" target="_blank" className="link"> <a href="https://beszel.dev/guide/notifications" target="_blank" className="link">
Shoutrrr Shoutrrr
</a>{" "} </a>{" "}
to integrate with popular notification services. to integrate with popular notification services.

View File

@@ -32,7 +32,7 @@ import { Separator } from "../ui/separator"
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip" import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip"
import { Button } from "../ui/button" import { Button } from "../ui/button"
import { Input } from "../ui/input" import { Input } from "../ui/input"
import { ChartAverage, ChartMax, Rows, TuxIcon, WindowsIcon, AppleIcon } from "../ui/icons" import { ChartAverage, ChartMax, Rows, TuxIcon, WindowsIcon, AppleIcon, FreeBsdIcon } from "../ui/icons"
import { useIntersectionObserver } from "@/lib/use-intersection-observer" import { useIntersectionObserver } from "@/lib/use-intersection-observer"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select"
import { timeTicks } from "d3-time" import { timeTicks } from "d3-time"
@@ -276,6 +276,10 @@ export default function SystemDetail({ name }: { name: string }) {
Icon: WindowsIcon, Icon: WindowsIcon,
value: system.info.k, value: system.info.k,
}, },
[Os.FreeBSD]: {
Icon: FreeBsdIcon,
value: system.info.k,
},
} }
let uptime: React.ReactNode let uptime: React.ReactNode

View File

@@ -38,6 +38,18 @@ export function AppleIcon(props: SVGProps<SVGSVGElement>) {
) )
} }
// Apache 2.0 https://github.com/Templarian/MaterialDesign/blob/master/LICENSE
export function FreeBsdIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg viewBox="0 0 24 24" {...props}>
<path
fill="currentColor"
d="M2.7 2C3.5 2 6 3.2 6 3.2 4.8 4 3.7 5 3 6.4 2.1 4.8 1.3 2.9 2 2.2l.7-.2m18.1.1c.4 0 .8 0 1 .2 1 1.1-2 5.8-2.4 6.4-.5.5-1.8 0-2.9-1-1-1.2-1.5-2.4-1-3 .4-.4 3.6-2.4 5.3-2.6m-8.8.5c1.3 0 2.5.2 3.7.7l-1 .7c-1 1-.6 2.8 1 4.4 1 1 2.1 1.6 3 1.6a2 2 0 0 0 1.5-.6l.7-1a9.7 9.7 0 1 1-18.6 3.8A9.7 9.7 0 0 1 12 2.7"
/>
</svg>
)
}
// ion icons (MIT) https://github.com/ionic-team/ionicons/blob/main/LICENSE // ion icons (MIT) https://github.com/ionic-team/ionicons/blob/main/LICENSE
export function DockerIcon(props: SVGProps<SVGSVGElement>) { export function DockerIcon(props: SVGProps<SVGSVGElement>) {
return ( return (

View File

@@ -2,7 +2,7 @@ export enum Os {
Linux = 0, Linux = 0,
Darwin, Darwin,
Windows, Windows,
// FreeBSD, FreeBSD,
} }
export enum ChartType { export enum ChartType {

View File

@@ -305,7 +305,7 @@ msgstr "Dokumentation"
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
msgid "Down" msgid "Down"
msgstr "" msgstr "Offline"
#: src/components/add-system.tsx #: src/components/add-system.tsx
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
@@ -461,7 +461,7 @@ msgstr "Anzeige- und Benachrichtigungseinstellungen verwalten."
#: src/components/add-system.tsx #: src/components/add-system.tsx
msgid "Manual setup instructions" msgid "Manual setup instructions"
msgstr "" msgstr "Anleitung zur manuellen Einrichtung"
#. Chart select field. Please try to keep this short. #. Chart select field. Please try to keep this short.
#: src/components/routes/system.tsx #: src/components/routes/system.tsx
@@ -727,7 +727,7 @@ msgstr "Tabelle"
#. Temperature label in systems table #. Temperature label in systems table
#: src/components/systems-table/systems-table.tsx #: src/components/systems-table/systems-table.tsx
msgid "Temp" msgid "Temp"
msgstr "" msgstr "Temperatur"
#: src/lib/utils.ts #: src/lib/utils.ts
#: src/components/routes/system.tsx #: src/components/routes/system.tsx

View File

@@ -1,6 +1,6 @@
package beszel package beszel
const ( const (
Version = "0.11.0" Version = "0.11.1"
AppName = "beszel" AppName = "beszel"
) )

View File

@@ -4,12 +4,12 @@ Beszel is a lightweight server monitoring platform that includes Docker statisti
It has a friendly web interface, simple configuration, and is ready to use out of the box. It supports automatic backup, multi-user, OAuth authentication, and API access. It has a friendly web interface, simple configuration, and is ready to use out of the box. It supports automatic backup, multi-user, OAuth authentication, and API access.
[![agent Docker Image Size](https://img.shields.io/docker/image-size/henrygd/beszel-agent/0.4.0?logo=docker&label=agent%20image%20size)](https://hub.docker.com/r/henrygd/beszel-agent) [![agent Docker Image Size](https://img.shields.io/docker/image-size/henrygd/beszel-agent/latest?logo=docker&label=agent%20image%20size)](https://hub.docker.com/r/henrygd/beszel-agent)
[![hub Docker Image Size](https://img.shields.io/docker/image-size/henrygd/beszel/0.4.0?logo=docker&label=hub%20image%20size)](https://hub.docker.com/r/henrygd/beszel) [![hub Docker Image Size](https://img.shields.io/docker/image-size/henrygd/beszel/latest?logo=docker&label=hub%20image%20size)](https://hub.docker.com/r/henrygd/beszel)
[![MIT license](https://img.shields.io/github/license/henrygd/beszel?color=%239944ee)](https://github.com/henrygd/beszel/blob/main/LICENSE) [![MIT license](https://img.shields.io/github/license/henrygd/beszel?color=%239944ee)](https://github.com/henrygd/beszel/blob/main/LICENSE)
[![Crowdin](https://badges.crowdin.net/beszel/localized.svg)](https://crowdin.com/project/beszel) [![Crowdin](https://badges.crowdin.net/beszel/localized.svg)](https://crowdin.com/project/beszel)
![Screenshot of beszel dashboard and system page](https://henrygd-assets.b-cdn.net/beszel/screenshot-new.png) ![Screenshot of Beszel dashboard and system page, side by side. The dashboard shows metrics from multiple connected systems, while the system page shows detailed metrics for a single system.](https://henrygd-assets.b-cdn.net/beszel/screenshot-new.png)
## Features ## Features

View File

@@ -2,7 +2,8 @@ param (
[switch]$Elevated, [switch]$Elevated,
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$Key, [string]$Key,
[int]$Port = 45876 [int]$Port = 45876,
[string]$AgentPath = ""
) )
# Check if key is provided or empty # Check if key is provided or empty
@@ -15,60 +16,147 @@ if ([string]::IsNullOrWhiteSpace($Key)) {
# Stop on first error # Stop on first error
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
#region Utility Functions
# Function to check if running as admin # Function to check if running as admin
function Test-Admin { function Test-Admin {
return ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) return ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
} }
# Non-admin tasks - install Scoop and Scoop apps - Only run if we're not in elevated mode # Function to check if a command exists
if (-not $Elevated) { function Test-CommandExists {
param (
[Parameter(Mandatory=$true)]
[string]$Command
)
return (Get-Command $Command -ErrorAction SilentlyContinue)
}
#endregion
#region Installation Methods
# Function to install Scoop
function Install-Scoop {
Write-Host "Installing Scoop..."
try { try {
# Check if Scoop is already installed Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression
if (Get-Command scoop -ErrorAction SilentlyContinue) {
if (-not (Test-CommandExists "scoop")) {
throw "Failed to install Scoop"
}
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 beszel-agent..."
scoop install beszel-agent | Out-Null
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 beszel-agent..."
winget install --exact --id henrygd.beszel-agent --accept-source-agreements --accept-package-agreements | Out-Null
# 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." Write-Host "Scoop is already installed."
} else {
Write-Host "Installing Scoop..."
Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression
if (-not (Get-Command scoop -ErrorAction SilentlyContinue)) {
throw "Failed to install Scoop"
}
}
# Check if git is already installed
if (Get-Command git -ErrorAction SilentlyContinue) {
Write-Host "Git is already installed."
} else {
Write-Host "Installing Git..."
scoop install git
if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
throw "Failed to install Git"
}
}
# Check if nssm is already installed
if (Get-Command nssm -ErrorAction SilentlyContinue) {
Write-Host "NSSM is already installed."
} else {
Write-Host "Installing NSSM..."
scoop install nssm
if (-not (Get-Command nssm -ErrorAction SilentlyContinue)) {
throw "Failed to install NSSM"
}
} }
# Add bucket and install agent # Install Git (required for Scoop buckets)
Write-Host "Adding beszel bucket..." Install-Git | Out-Null
scoop bucket add beszel https://github.com/henrygd/beszel-scoops
Write-Host "Installing beszel-agent..." # Install NSSM
scoop install beszel-agent Install-NSSM -Method "Scoop" | Out-Null
if (-not (Get-Command beszel-agent -ErrorAction SilentlyContinue)) { # Install beszel-agent
throw "Failed to install beszel-agent" $agentPath = Install-BeszelAgentWithScoop
}
return $agentPath
} }
catch { catch {
Write-Host "ERROR: $($_.Exception.Message)" -ForegroundColor Red Write-Host "ERROR: $($_.Exception.Message)" -ForegroundColor Red
@@ -77,27 +165,48 @@ if (-not $Elevated) {
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
exit 1 exit 1
} }
}
# Check if we need admin privileges for the NSSM part # Function to install using WinGet
if (-not (Test-Admin)) { function Install-WithWinGet {
Write-Host "Admin privileges required for NSSM. Relaunching as admin..." -ForegroundColor Yellow param (
Write-Host "Check service status with 'nssm status beszel-agent'" [string]$Key,
Write-Host "Edit service configuration with 'nssm edit beszel-agent'" [int]$Port
)
try {
# Install NSSM
Install-NSSM -Method "WinGet" | Out-Null
# Relaunch the script with the -Elevated switch and pass parameters # Install beszel-agent
Start-Process powershell.exe -Verb RunAs -ArgumentList "-File `"$PSCommandPath`" -Elevated -Key `"$Key`" -Port $Port" $agentPath = Install-BeszelAgentWithWinGet
exit
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
} }
} }
# Admin tasks - service installation and firewall rules #endregion
try {
$agentPath = Join-Path -Path $(scoop prefix beszel-agent) -ChildPath "beszel-agent.exe" #region Service Configuration
if (-not $agentPath) {
throw "Could not find beszel-agent executable. Make sure it was properly installed." # 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)]
[int]$Port
)
# Install and configure the service
Write-Host "Installing beszel-agent service..." Write-Host "Installing beszel-agent service..."
# Check if service already exists # Check if service already exists
@@ -112,7 +221,7 @@ try {
} }
} }
nssm install beszel-agent $agentPath nssm install beszel-agent $AgentPath
if ($LASTEXITCODE -ne 0) { if ($LASTEXITCODE -ne 0) {
throw "Failed to install beszel-agent service" throw "Failed to install beszel-agent service"
} }
@@ -129,6 +238,14 @@ try {
$logFile = "$logDir\beszel-agent.log" $logFile = "$logDir\beszel-agent.log"
nssm set beszel-agent AppStdout $logFile nssm set beszel-agent AppStdout $logFile
nssm set beszel-agent AppStderr $logFile nssm 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 # Create a firewall rule if it doesn't exist
$ruleName = "Allow beszel-agent" $ruleName = "Allow beszel-agent"
@@ -154,31 +271,121 @@ try {
Write-Host "Warning: Failed to create firewall rule: $($_.Exception.Message)" -ForegroundColor Yellow 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 Write-Host "You may need to manually create a firewall rule for port $Port." -ForegroundColor Yellow
} }
Write-Host "Starting beszel-agent service..."
nssm start beszel-agent
if ($LASTEXITCODE -ne 0) {
throw "Failed to start beszel-agent service"
}
Write-Host "Checking beszel-agent service status..."
Start-Sleep -Seconds 5 # Allow time to start before checking status
$serviceStatus = nssm status beszel-agent
if ($serviceStatus -eq "SERVICE_RUNNING") {
Write-Host "Success! The beszel-agent service is running properly." -ForegroundColor Green
} 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
}
}
catch {
Write-Host "ERROR: $($_.Exception.Message)" -ForegroundColor Red
Write-Host "Installation failed. Please check the error message above." -ForegroundColor Red
} }
# Pause to see results before exit if this is an elevated window # Function to start and monitor the service
function Start-BeszelAgentService {
Write-Host "Starting beszel-agent service..."
nssm 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 = nssm 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
# Non-admin tasks - Only run if we're not in elevated mode
if (-not $Elevated) {
try {
# Determine installation method
$AgentPath = ""
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
}
# Check if we need admin privileges for the NSSM part
if (-not (Test-Admin)) {
Write-Host "Admin privileges required for NSSM. 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'"
# Relaunch the script with the -Elevated switch and pass parameters
Start-Process powershell.exe -Verb RunAs -ArgumentList "-File `"$PSCommandPath`" -Elevated -Key `"$Key`" -Port $Port -AgentPath `"$AgentPath`""
exit
}
}
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
}
}
# Admin tasks - service installation and firewall rules
if ($Elevated) { if ($Elevated) {
try {
if (-not $AgentPath) {
throw "Could not find beszel-agent executable. Make sure it was properly installed."
}
# Install the service
Install-NSSMService -AgentPath $AgentPath -Key $Key -Port $Port
# Configure firewall
Configure-Firewall -Port $Port
# Start the service
Start-BeszelAgentService
}
catch {
Write-Host "ERROR: $($_.Exception.Message)" -ForegroundColor Red
Write-Host "Installation failed. Please check the error message above." -ForegroundColor Red
}
# Pause to see results before exit if this is an elevated window
Write-Host "Press any key to exit..." -ForegroundColor Cyan Write-Host "Press any key to exit..." -ForegroundColor Cyan
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
} }
#endregion

View File

@@ -8,7 +8,53 @@ is_openwrt() {
cat /etc/os-release | grep -q "OpenWrt" cat /etc/os-release | grep -q "OpenWrt"
} }
# Function to ensure the proxy URL ends with a / # 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() { ensure_trailing_slash() {
if [ -n "$1" ]; then if [ -n "$1" ]; then
case "$1" in case "$1" in
@@ -20,7 +66,7 @@ ensure_trailing_slash() {
fi fi
} }
# Define default values # Default values
PORT=45876 PORT=45876
UNINSTALL=false UNINSTALL=false
GITHUB_URL="https://github.com" GITHUB_URL="https://github.com"
@@ -141,6 +187,9 @@ done
# Uninstall process # Uninstall process
if [ "$UNINSTALL" = true ]; then if [ "$UNINSTALL" = true ]; then
# Clean up SELinux contexts before removing files
cleanup_selinux_context
if is_alpine; then if is_alpine; then
echo "Stopping and disabling the agent service..." echo "Stopping and disabling the agent service..."
rc-service beszel-agent stop rc-service beszel-agent stop
@@ -334,6 +383,9 @@ mv beszel-agent /opt/beszel-agent/beszel-agent
chown beszel:beszel /opt/beszel-agent/beszel-agent chown beszel:beszel /opt/beszel-agent/beszel-agent
chmod 755 /opt/beszel-agent/beszel-agent chmod 755 /opt/beszel-agent/beszel-agent
# Set SELinux context if needed
set_selinux_context
# Cleanup # Cleanup
rm -rf "$TEMP_DIR" rm -rf "$TEMP_DIR"
@@ -528,7 +580,6 @@ StateDirectory=beszel-agent
KeyringMode=private KeyringMode=private
LockPersonality=yes LockPersonality=yes
NoNewPrivileges=yes NoNewPrivileges=yes
PrivateTmp=yes
ProtectClock=yes ProtectClock=yes
ProtectHome=read-only ProtectHome=read-only
ProtectHostname=yes ProtectHostname=yes
@@ -548,6 +599,37 @@ EOF
systemctl enable beszel-agent.service systemctl enable beszel-agent.service
systemctl start beszel-agent.service systemctl start beszel-agent.service
# Create the update script
echo "Creating the update script..."
cat >/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 # Prompt for auto-update setup
if [ "$AUTO_UPDATE_FLAG" = "true" ]; then if [ "$AUTO_UPDATE_FLAG" = "true" ]; then
AUTO_UPDATE="y" AUTO_UPDATE="y"
@@ -571,7 +653,7 @@ Wants=beszel-agent.service
[Service] [Service]
Type=oneshot Type=oneshot
ExecStart=/bin/sh -c '/opt/beszel-agent/beszel-agent update | grep -q "Successfully updated" && (echo "Update found, restarting beszel-agent" && systemctl restart beszel-agent) || echo "No updates found"' ExecStart=/opt/beszel-agent/run-update.sh
EOF EOF
# Create systemd timer for the daily update # Create systemd timer for the daily update