mirror of
https://github.com/henrygd/beszel.git
synced 2026-03-24 06:26:17 +01:00
Compare commits
7 Commits
temp-pr-16
...
42da1e5a52
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42da1e5a52 | ||
|
|
afcae025ae | ||
|
|
1de36625a4 | ||
|
|
a2b6c7f5e6 | ||
|
|
799c7b077a | ||
|
|
cb5f944de6 | ||
|
|
23c4958145 |
@@ -158,9 +158,7 @@ nfpms:
|
|||||||
- debconf
|
- debconf
|
||||||
scripts:
|
scripts:
|
||||||
templates: ./supplemental/debian/templates
|
templates: ./supplemental/debian/templates
|
||||||
# Currently broken due to a bug in goreleaser
|
config: ./supplemental/debian/config.sh
|
||||||
# https://github.com/goreleaser/goreleaser/issues/5487
|
|
||||||
#config: ./supplemental/debian/config.sh
|
|
||||||
|
|
||||||
scoops:
|
scoops:
|
||||||
- ids: [beszel-agent]
|
- ids: [beszel-agent]
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ func GetBatteryStats() (batteryPercent uint8, batteryState uint8, err error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
totalCapacity += bat.Full
|
totalCapacity += bat.Full
|
||||||
totalCharge += bat.Current
|
totalCharge += min(bat.Current, bat.Full)
|
||||||
if bat.State.Raw >= 0 {
|
if bat.State.Raw >= 0 {
|
||||||
batteryState = uint8(bat.State.Raw)
|
batteryState = uint8(bat.State.Raw)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ func (sm *SmartManager) ScanDevices(force bool) error {
|
|||||||
configuredDevices = parsedDevices
|
configuredDevices = parsedDevices
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
cmd := exec.CommandContext(ctx, sm.binPath, "--scan", "-j")
|
cmd := exec.CommandContext(ctx, sm.binPath, "--scan", "-j")
|
||||||
@@ -515,10 +515,12 @@ func (sm *SmartManager) CollectSmart(deviceInfo *DeviceInfo) error {
|
|||||||
// smartctlArgs returns the arguments for the smartctl command
|
// smartctlArgs returns the arguments for the smartctl command
|
||||||
// based on the device type and whether to include standby mode
|
// based on the device type and whether to include standby mode
|
||||||
func (sm *SmartManager) smartctlArgs(deviceInfo *DeviceInfo, includeStandby bool) []string {
|
func (sm *SmartManager) smartctlArgs(deviceInfo *DeviceInfo, includeStandby bool) []string {
|
||||||
args := make([]string, 0, 7)
|
args := make([]string, 0, 9)
|
||||||
|
var deviceType, parserType string
|
||||||
|
|
||||||
if deviceInfo != nil {
|
if deviceInfo != nil {
|
||||||
deviceType := strings.ToLower(deviceInfo.Type)
|
deviceType = strings.ToLower(deviceInfo.Type)
|
||||||
|
parserType = strings.ToLower(deviceInfo.parserType)
|
||||||
// types sometimes misidentified in scan; see github.com/henrygd/beszel/issues/1345
|
// types sometimes misidentified in scan; see github.com/henrygd/beszel/issues/1345
|
||||||
if deviceType != "" && deviceType != "scsi" && deviceType != "ata" {
|
if deviceType != "" && deviceType != "scsi" && deviceType != "ata" {
|
||||||
args = append(args, "-d", deviceInfo.Type)
|
args = append(args, "-d", deviceInfo.Type)
|
||||||
@@ -526,6 +528,13 @@ func (sm *SmartManager) smartctlArgs(deviceInfo *DeviceInfo, includeStandby bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
args = append(args, "-a", "--json=c")
|
args = append(args, "-a", "--json=c")
|
||||||
|
effectiveType := parserType
|
||||||
|
if effectiveType == "" {
|
||||||
|
effectiveType = deviceType
|
||||||
|
}
|
||||||
|
if effectiveType == "sat" || effectiveType == "ata" {
|
||||||
|
args = append(args, "-l", "devstat")
|
||||||
|
}
|
||||||
|
|
||||||
if includeStandby {
|
if includeStandby {
|
||||||
args = append(args, "-n", "standby")
|
args = append(args, "-n", "standby")
|
||||||
@@ -829,6 +838,11 @@ func (sm *SmartManager) parseSmartForSata(output []byte) (bool, int) {
|
|||||||
smartData.FirmwareVersion = data.FirmwareVersion
|
smartData.FirmwareVersion = data.FirmwareVersion
|
||||||
smartData.Capacity = data.UserCapacity.Bytes
|
smartData.Capacity = data.UserCapacity.Bytes
|
||||||
smartData.Temperature = data.Temperature.Current
|
smartData.Temperature = data.Temperature.Current
|
||||||
|
if smartData.Temperature == 0 {
|
||||||
|
if temp, ok := temperatureFromAtaDeviceStatistics(data.AtaDeviceStatistics); ok {
|
||||||
|
smartData.Temperature = temp
|
||||||
|
}
|
||||||
|
}
|
||||||
smartData.SmartStatus = getSmartStatus(smartData.Temperature, data.SmartStatus.Passed)
|
smartData.SmartStatus = getSmartStatus(smartData.Temperature, data.SmartStatus.Passed)
|
||||||
smartData.DiskName = data.Device.Name
|
smartData.DiskName = data.Device.Name
|
||||||
smartData.DiskType = data.Device.Type
|
smartData.DiskType = data.Device.Type
|
||||||
@@ -867,6 +881,36 @@ func getSmartStatus(temperature uint8, passed bool) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func temperatureFromAtaDeviceStatistics(stats smart.AtaDeviceStatistics) (uint8, bool) {
|
||||||
|
entry := findAtaDeviceStatisticsEntry(stats, 5, "Current Temperature")
|
||||||
|
if entry == nil || entry.Value == nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
if *entry.Value > 255 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return uint8(*entry.Value), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// findAtaDeviceStatisticsEntry centralizes ATA devstat lookups so additional
|
||||||
|
// metrics can be pulled from the same structure in the future.
|
||||||
|
func findAtaDeviceStatisticsEntry(stats smart.AtaDeviceStatistics, pageNumber uint8, entryName string) *smart.AtaDeviceStatisticsEntry {
|
||||||
|
for pageIdx := range stats.Pages {
|
||||||
|
page := &stats.Pages[pageIdx]
|
||||||
|
if page.Number != pageNumber {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for entryIdx := range page.Table {
|
||||||
|
entry := &page.Table[entryIdx]
|
||||||
|
if !strings.EqualFold(entry.Name, entryName) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (sm *SmartManager) parseSmartForScsi(output []byte) (bool, int) {
|
func (sm *SmartManager) parseSmartForScsi(output []byte) (bool, int) {
|
||||||
var data smart.SmartInfoForScsi
|
var data smart.SmartInfoForScsi
|
||||||
|
|
||||||
|
|||||||
@@ -89,6 +89,39 @@ func TestParseSmartForSata(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseSmartForSataDeviceStatisticsTemperature(t *testing.T) {
|
||||||
|
jsonPayload := []byte(`{
|
||||||
|
"smartctl": {"exit_status": 0},
|
||||||
|
"device": {"name": "/dev/sdb", "type": "sat"},
|
||||||
|
"model_name": "SanDisk SSD U110 16GB",
|
||||||
|
"serial_number": "DEVSTAT123",
|
||||||
|
"firmware_version": "U21B001",
|
||||||
|
"user_capacity": {"bytes": 16013942784},
|
||||||
|
"smart_status": {"passed": true},
|
||||||
|
"ata_smart_attributes": {"table": []},
|
||||||
|
"ata_device_statistics": {
|
||||||
|
"pages": [
|
||||||
|
{
|
||||||
|
"number": 5,
|
||||||
|
"name": "Temperature Statistics",
|
||||||
|
"table": [
|
||||||
|
{"name": "Current Temperature", "value": 22, "flags": {"valid": true}}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
sm := &SmartManager{SmartDataMap: make(map[string]*smart.SmartData)}
|
||||||
|
hasData, exitStatus := sm.parseSmartForSata(jsonPayload)
|
||||||
|
require.True(t, hasData)
|
||||||
|
assert.Equal(t, 0, exitStatus)
|
||||||
|
|
||||||
|
deviceData, ok := sm.SmartDataMap["DEVSTAT123"]
|
||||||
|
require.True(t, ok, "expected smart data entry for serial DEVSTAT123")
|
||||||
|
assert.Equal(t, uint8(22), deviceData.Temperature)
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseSmartForSataParentheticalRawValue(t *testing.T) {
|
func TestParseSmartForSataParentheticalRawValue(t *testing.T) {
|
||||||
jsonPayload := []byte(`{
|
jsonPayload := []byte(`{
|
||||||
"smartctl": {"exit_status": 0},
|
"smartctl": {"exit_status": 0},
|
||||||
@@ -267,15 +300,21 @@ func TestSmartctlArgs(t *testing.T) {
|
|||||||
|
|
||||||
sataDevice := &DeviceInfo{Name: "/dev/sda", Type: "sat"}
|
sataDevice := &DeviceInfo{Name: "/dev/sda", Type: "sat"}
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
[]string{"-d", "sat", "-a", "--json=c", "-n", "standby", "/dev/sda"},
|
[]string{"-d", "sat", "-a", "--json=c", "-l", "devstat", "-n", "standby", "/dev/sda"},
|
||||||
sm.smartctlArgs(sataDevice, true),
|
sm.smartctlArgs(sataDevice, true),
|
||||||
)
|
)
|
||||||
|
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
[]string{"-d", "sat", "-a", "--json=c", "/dev/sda"},
|
[]string{"-d", "sat", "-a", "--json=c", "-l", "devstat", "/dev/sda"},
|
||||||
sm.smartctlArgs(sataDevice, false),
|
sm.smartctlArgs(sataDevice, false),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
nvmeDevice := &DeviceInfo{Name: "/dev/nvme0", Type: "nvme"}
|
||||||
|
assert.Equal(t,
|
||||||
|
[]string{"-d", "nvme", "-a", "--json=c", "-n", "standby", "/dev/nvme0"},
|
||||||
|
sm.smartctlArgs(nvmeDevice, true),
|
||||||
|
)
|
||||||
|
|
||||||
assert.Equal(t,
|
assert.Equal(t,
|
||||||
[]string{"-a", "--json=c", "-n", "standby"},
|
[]string{"-a", "--json=c", "-n", "standby"},
|
||||||
sm.smartctlArgs(nil, true),
|
sm.smartctlArgs(nil, true),
|
||||||
@@ -516,18 +555,18 @@ func TestUpdateSmartDevicesPreservesRAIDDrives(t *testing.T) {
|
|||||||
},
|
},
|
||||||
SmartDataMap: map[string]*smart.SmartData{
|
SmartDataMap: map[string]*smart.SmartData{
|
||||||
"serial-0": {
|
"serial-0": {
|
||||||
DiskName: "/dev/sda",
|
DiskName: "/dev/sda",
|
||||||
DiskType: "megaraid,0",
|
DiskType: "megaraid,0",
|
||||||
SerialNumber: "serial-0",
|
SerialNumber: "serial-0",
|
||||||
},
|
},
|
||||||
"serial-1": {
|
"serial-1": {
|
||||||
DiskName: "/dev/sda",
|
DiskName: "/dev/sda",
|
||||||
DiskType: "megaraid,1",
|
DiskType: "megaraid,1",
|
||||||
SerialNumber: "serial-1",
|
SerialNumber: "serial-1",
|
||||||
},
|
},
|
||||||
"serial-stale": {
|
"serial-stale": {
|
||||||
DiskName: "/dev/sda",
|
DiskName: "/dev/sda",
|
||||||
DiskType: "megaraid,2",
|
DiskType: "megaraid,2",
|
||||||
SerialNumber: "serial-stale",
|
SerialNumber: "serial-stale",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
package agent
|
package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/henrygd/beszel/internal/ghupdate"
|
"github.com/henrygd/beszel/internal/ghupdate"
|
||||||
)
|
)
|
||||||
@@ -108,12 +106,12 @@ func Update(useMirror bool) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6) Fix SELinux context if necessary
|
// Fix SELinux context if necessary
|
||||||
if err := handleSELinuxContext(exePath); err != nil {
|
if err := ghupdate.HandleSELinuxContext(exePath); err != nil {
|
||||||
ghupdate.ColorPrintf(ghupdate.ColorYellow, "Warning: SELinux context handling: %v", err)
|
ghupdate.ColorPrintf(ghupdate.ColorYellow, "Warning: SELinux context handling: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7) Restart service if running under a recognised init system
|
// Restart service if running under a recognised init system
|
||||||
if r := detectRestarter(); r != nil {
|
if r := detectRestarter(); r != nil {
|
||||||
if err := r.Restart(); err != nil {
|
if err := r.Restart(); err != nil {
|
||||||
ghupdate.ColorPrintf(ghupdate.ColorYellow, "Warning: failed to restart service: %v", err)
|
ghupdate.ColorPrintf(ghupdate.ColorYellow, "Warning: failed to restart service: %v", err)
|
||||||
@@ -128,41 +126,3 @@ func Update(useMirror bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleSELinuxContext restores or applies the correct SELinux label to the binary.
|
|
||||||
func handleSELinuxContext(path string) error {
|
|
||||||
out, err := exec.Command("getenforce").Output()
|
|
||||||
if err != nil {
|
|
||||||
// SELinux not enabled or getenforce not available
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
state := strings.TrimSpace(string(out))
|
|
||||||
if state == "Disabled" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ghupdate.ColorPrint(ghupdate.ColorYellow, "SELinux is enabled; applying context…")
|
|
||||||
var errs []string
|
|
||||||
|
|
||||||
// Try persistent context via semanage+restorecon
|
|
||||||
if semanagePath, err := exec.LookPath("semanage"); err == nil {
|
|
||||||
if err := exec.Command(semanagePath, "fcontext", "-a", "-t", "bin_t", path).Run(); err != nil {
|
|
||||||
errs = append(errs, "semanage fcontext failed: "+err.Error())
|
|
||||||
} else if restoreconPath, err := exec.LookPath("restorecon"); err == nil {
|
|
||||||
if err := exec.Command(restoreconPath, "-v", path).Run(); err != nil {
|
|
||||||
errs = append(errs, "restorecon failed: "+err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to temporary context via chcon
|
|
||||||
if chconPath, err := exec.LookPath("chcon"); err == nil {
|
|
||||||
if err := exec.Command(chconPath, "-t", "bin_t", path).Run(); err != nil {
|
|
||||||
errs = append(errs, "chcon failed: "+err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(errs) > 0 {
|
|
||||||
return fmt.Errorf("SELinux context errors: %s", strings.Join(errs, "; "))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -130,10 +130,23 @@ type SummaryInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AtaSmartAttributes struct {
|
type AtaSmartAttributes struct {
|
||||||
// Revision int `json:"revision"`
|
|
||||||
Table []AtaSmartAttribute `json:"table"`
|
Table []AtaSmartAttribute `json:"table"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AtaDeviceStatistics struct {
|
||||||
|
Pages []AtaDeviceStatisticsPage `json:"pages"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AtaDeviceStatisticsPage struct {
|
||||||
|
Number uint8 `json:"number"`
|
||||||
|
Table []AtaDeviceStatisticsEntry `json:"table"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AtaDeviceStatisticsEntry struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Value *uint64 `json:"value,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type AtaSmartAttribute struct {
|
type AtaSmartAttribute struct {
|
||||||
ID uint16 `json:"id"`
|
ID uint16 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@@ -343,7 +356,8 @@ type SmartInfoForSata struct {
|
|||||||
SmartStatus SmartStatusInfo `json:"smart_status"`
|
SmartStatus SmartStatusInfo `json:"smart_status"`
|
||||||
// AtaSmartData AtaSmartData `json:"ata_smart_data"`
|
// AtaSmartData AtaSmartData `json:"ata_smart_data"`
|
||||||
// AtaSctCapabilities AtaSctCapabilities `json:"ata_sct_capabilities"`
|
// AtaSctCapabilities AtaSctCapabilities `json:"ata_sct_capabilities"`
|
||||||
AtaSmartAttributes AtaSmartAttributes `json:"ata_smart_attributes"`
|
AtaSmartAttributes AtaSmartAttributes `json:"ata_smart_attributes"`
|
||||||
|
AtaDeviceStatistics AtaDeviceStatistics `json:"ata_device_statistics"`
|
||||||
// PowerOnTime PowerOnTimeInfo `json:"power_on_time"`
|
// PowerOnTime PowerOnTimeInfo `json:"power_on_time"`
|
||||||
// PowerCycleCount uint16 `json:"power_cycle_count"`
|
// PowerCycleCount uint16 `json:"power_cycle_count"`
|
||||||
Temperature TemperatureInfo `json:"temperature"`
|
Temperature TemperatureInfo `json:"temperature"`
|
||||||
|
|||||||
66
internal/ghupdate/selinux.go
Normal file
66
internal/ghupdate/selinux.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package ghupdate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandleSELinuxContext restores or applies the correct SELinux label to the binary.
|
||||||
|
func HandleSELinuxContext(path string) error {
|
||||||
|
out, err := exec.Command("getenforce").Output()
|
||||||
|
if err != nil {
|
||||||
|
// SELinux not enabled or getenforce not available
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
state := strings.TrimSpace(string(out))
|
||||||
|
if state == "Disabled" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorPrint(ColorYellow, "SELinux is enabled; applying context…")
|
||||||
|
|
||||||
|
// Try persistent context via semanage+restorecon
|
||||||
|
if success := trySemanageRestorecon(path); success {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to temporary context via chcon
|
||||||
|
if chconPath, err := exec.LookPath("chcon"); err == nil {
|
||||||
|
if err := exec.Command(chconPath, "-t", "bin_t", path).Run(); err != nil {
|
||||||
|
return fmt.Errorf("chcon failed: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("no SELinux tools available (semanage/restorecon or chcon)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// trySemanageRestorecon attempts to set persistent SELinux context using semanage and restorecon.
|
||||||
|
// Returns true if successful, false otherwise.
|
||||||
|
func trySemanageRestorecon(path string) bool {
|
||||||
|
semanagePath, err := exec.LookPath("semanage")
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreconPath, err := exec.LookPath("restorecon")
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to add the fcontext rule; if it already exists, try to modify it
|
||||||
|
if err := exec.Command(semanagePath, "fcontext", "-a", "-t", "bin_t", path).Run(); err != nil {
|
||||||
|
// Rule may already exist, try modify instead
|
||||||
|
if err := exec.Command(semanagePath, "fcontext", "-m", "-t", "bin_t", path).Run(); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the context with restorecon
|
||||||
|
if err := exec.Command(restoreconPath, "-v", path).Run(); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
53
internal/ghupdate/selinux_test.go
Normal file
53
internal/ghupdate/selinux_test.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package ghupdate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHandleSELinuxContext_NoSELinux(t *testing.T) {
|
||||||
|
// Skip on SELinux systems - this test is for non-SELinux behavior
|
||||||
|
if _, err := exec.LookPath("getenforce"); err == nil {
|
||||||
|
t.Skip("skipping on SELinux-enabled system")
|
||||||
|
}
|
||||||
|
|
||||||
|
// On systems without SELinux, getenforce will fail and the function
|
||||||
|
// should return nil without error
|
||||||
|
tempFile := filepath.Join(t.TempDir(), "test-binary")
|
||||||
|
if err := os.WriteFile(tempFile, []byte("test"), 0755); err != nil {
|
||||||
|
t.Fatalf("failed to create temp file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := HandleSELinuxContext(tempFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("HandleSELinuxContext() on non-SELinux system returned error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleSELinuxContext_InvalidPath(t *testing.T) {
|
||||||
|
// Skip on SELinux systems - this test is for non-SELinux behavior
|
||||||
|
if _, err := exec.LookPath("getenforce"); err == nil {
|
||||||
|
t.Skip("skipping on SELinux-enabled system")
|
||||||
|
}
|
||||||
|
|
||||||
|
// On non-SELinux systems, getenforce fails early so even invalid paths succeed
|
||||||
|
err := HandleSELinuxContext("/nonexistent/path/binary")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("HandleSELinuxContext() with invalid path on non-SELinux system returned error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrySemanageRestorecon_NoTools(t *testing.T) {
|
||||||
|
// Skip if semanage is available (we don't want to modify system SELinux policy)
|
||||||
|
if _, err := exec.LookPath("semanage"); err == nil {
|
||||||
|
t.Skip("skipping on system with semanage available")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should return false when semanage is not available
|
||||||
|
result := trySemanageRestorecon("/some/path")
|
||||||
|
if result {
|
||||||
|
t.Error("trySemanageRestorecon() returned true when semanage is not available")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,6 +45,11 @@ func Update(cmd *cobra.Command, _ []string) {
|
|||||||
fmt.Printf("Warning: failed to set executable permissions: %v\n", err)
|
fmt.Printf("Warning: failed to set executable permissions: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fix SELinux context if necessary
|
||||||
|
if err := ghupdate.HandleSELinuxContext(exePath); err != nil {
|
||||||
|
ghupdate.ColorPrintf(ghupdate.ColorYellow, "Warning: SELinux context handling: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Try to restart the service if it's running
|
// Try to restart the service if it's running
|
||||||
restartService()
|
restartService()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,10 +49,12 @@ export function AddSystemButton({ className }: { className?: string }) {
|
|||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant="outline" className={cn("flex gap-1 max-xs:h-[2.4rem]", className)}>
|
<Button variant="outline" className={cn("flex gap-1 max-xs:h-[2.4rem]", className)}>
|
||||||
<PlusIcon className="h-4 w-4 -ms-1" />
|
<PlusIcon className="h-4 w-4 450:-ms-1" />
|
||||||
<Trans>
|
<span className="hidden 450:inline">
|
||||||
Add <span className="hidden sm:inline">System</span>
|
<Trans>
|
||||||
</Trans>
|
Add <span className="hidden sm:inline">System</span>
|
||||||
|
</Trans>
|
||||||
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
{opened.current && <SystemDialog setOpen={setOpen} />}
|
{opened.current && <SystemDialog setOpen={setOpen} />}
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ export default function Navbar() {
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
<AddSystemButton className="ms-2 hidden 450:flex" />
|
<AddSystemButton className="ms-2" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,13 +9,6 @@ SERVICE_USER=beszel
|
|||||||
|
|
||||||
. /usr/share/debconf/confmodule
|
. /usr/share/debconf/confmodule
|
||||||
|
|
||||||
# This would normally be in the config control file, however this is currently
|
|
||||||
# broken in goreleaser. Temporarily do it here.
|
|
||||||
# https://github.com/goreleaser/goreleaser/issues/5487
|
|
||||||
db_version 2.0
|
|
||||||
db_input high beszel-agent/key || true
|
|
||||||
db_go
|
|
||||||
|
|
||||||
# Create group and user
|
# Create group and user
|
||||||
if ! getent group "$SERVICE_USER" >/dev/null; then
|
if ! getent group "$SERVICE_USER" >/dev/null; then
|
||||||
echo "Creating $SERVICE_USER group"
|
echo "Creating $SERVICE_USER group"
|
||||||
|
|||||||
@@ -373,6 +373,20 @@ else
|
|||||||
BIN_PATH="/opt/beszel-agent/beszel-agent"
|
BIN_PATH="/opt/beszel-agent/beszel-agent"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Stop existing service if it exists (for upgrades)
|
||||||
|
if [ -f "$BIN_PATH" ]; then
|
||||||
|
echo "Existing installation detected. Stopping service for upgrade..."
|
||||||
|
if is_alpine; then
|
||||||
|
rc-service beszel-agent stop 2>/dev/null || true
|
||||||
|
elif is_openwrt; then
|
||||||
|
/etc/init.d/beszel-agent stop 2>/dev/null || true
|
||||||
|
elif is_freebsd; then
|
||||||
|
service beszel-agent stop 2>/dev/null || true
|
||||||
|
else
|
||||||
|
systemctl stop beszel-agent.service 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Uninstall process
|
# Uninstall process
|
||||||
if [ "$UNINSTALL" = true ]; then
|
if [ "$UNINSTALL" = true ]; then
|
||||||
# Clean up SELinux contexts before removing files
|
# Clean up SELinux contexts before removing files
|
||||||
@@ -507,10 +521,14 @@ else
|
|||||||
echo "Warning: Please ensure 'tar' and 'curl' and 'sha256sum (coreutils)' are installed."
|
echo "Warning: Please ensure 'tar' and 'curl' and 'sha256sum (coreutils)' are installed."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# If no SSH key is provided, ask for the SSH key interactively
|
# If no SSH key is provided, ask for the SSH key interactively (skip if upgrading)
|
||||||
if [ -z "$KEY" ]; then
|
if [ -z "$KEY" ]; then
|
||||||
printf "Enter your SSH key: "
|
if [ -f "$BIN_PATH" ]; then
|
||||||
read KEY
|
echo "Upgrading existing installation. Using existing service configuration."
|
||||||
|
else
|
||||||
|
printf "Enter your SSH key: "
|
||||||
|
read KEY
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Remove newlines from KEY
|
# Remove newlines from KEY
|
||||||
@@ -667,6 +685,11 @@ if ! tar -xzf "$FILE_NAME" beszel-agent; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -f "$BIN_PATH" ]; then
|
||||||
|
echo "Backing up existing binary..."
|
||||||
|
cp "$BIN_PATH" "$BIN_PATH.bak"
|
||||||
|
fi
|
||||||
|
|
||||||
mv beszel-agent "$BIN_PATH"
|
mv beszel-agent "$BIN_PATH"
|
||||||
chown beszel:beszel "$BIN_PATH"
|
chown beszel:beszel "$BIN_PATH"
|
||||||
chmod 755 "$BIN_PATH"
|
chmod 755 "$BIN_PATH"
|
||||||
@@ -695,8 +718,9 @@ detect_nvidia_devices() {
|
|||||||
|
|
||||||
# Modify service installation part, add Alpine check before systemd service creation
|
# Modify service installation part, add Alpine check before systemd service creation
|
||||||
if is_alpine; then
|
if is_alpine; then
|
||||||
echo "Creating OpenRC service for Alpine Linux..."
|
if [ ! -f /etc/init.d/beszel-agent ]; then
|
||||||
cat >/etc/init.d/beszel-agent <<EOF
|
echo "Creating OpenRC service for Alpine Linux..."
|
||||||
|
cat >/etc/init.d/beszel-agent <<EOF
|
||||||
#!/sbin/openrc-run
|
#!/sbin/openrc-run
|
||||||
|
|
||||||
name="beszel-agent"
|
name="beszel-agent"
|
||||||
@@ -722,9 +746,11 @@ depend() {
|
|||||||
after firewall
|
after firewall
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
chmod +x /etc/init.d/beszel-agent
|
||||||
chmod +x /etc/init.d/beszel-agent
|
rc-update add beszel-agent default
|
||||||
rc-update add beszel-agent default
|
else
|
||||||
|
echo "Alpine OpenRC service file already exists. Skipping creation."
|
||||||
|
fi
|
||||||
|
|
||||||
# Create log files with proper permissions
|
# Create log files with proper permissions
|
||||||
touch /var/log/beszel-agent.log /var/log/beszel-agent.err
|
touch /var/log/beszel-agent.log /var/log/beszel-agent.err
|
||||||
@@ -771,8 +797,9 @@ EOF
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
elif is_openwrt; then
|
elif is_openwrt; then
|
||||||
echo "Creating procd init script service for OpenWRT..."
|
if [ ! -f /etc/init.d/beszel-agent ]; then
|
||||||
cat >/etc/init.d/beszel-agent <<EOF
|
echo "Creating procd init script service for OpenWRT..."
|
||||||
|
cat >/etc/init.d/beszel-agent <<EOF
|
||||||
#!/bin/sh /etc/rc.common
|
#!/bin/sh /etc/rc.common
|
||||||
|
|
||||||
USE_PROCD=1
|
USE_PROCD=1
|
||||||
@@ -800,10 +827,12 @@ update() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
|
# Enable the service
|
||||||
# Enable the service
|
chmod +x /etc/init.d/beszel-agent
|
||||||
chmod +x /etc/init.d/beszel-agent
|
/etc/init.d/beszel-agent enable
|
||||||
/etc/init.d/beszel-agent enable
|
else
|
||||||
|
echo "OpenWRT init script already exists. Skipping creation."
|
||||||
|
fi
|
||||||
|
|
||||||
# Start the service
|
# Start the service
|
||||||
/etc/init.d/beszel-agent restart
|
/etc/init.d/beszel-agent restart
|
||||||
@@ -841,25 +870,33 @@ EOF
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
elif is_freebsd; then
|
elif is_freebsd; then
|
||||||
echo "Creating FreeBSD rc service..."
|
echo "Checking for existing FreeBSD service configuration..."
|
||||||
|
|
||||||
# Create environment configuration file with proper permissions
|
# Create environment configuration file with proper permissions if it doesn't exist
|
||||||
echo "Creating environment configuration file..."
|
if [ ! -f "$AGENT_DIR/env" ]; then
|
||||||
cat >"$AGENT_DIR/env" <<EOF
|
echo "Creating environment configuration file..."
|
||||||
|
cat >"$AGENT_DIR/env" <<EOF
|
||||||
LISTEN=$PORT
|
LISTEN=$PORT
|
||||||
KEY="$KEY"
|
KEY="$KEY"
|
||||||
TOKEN=$TOKEN
|
TOKEN=$TOKEN
|
||||||
HUB_URL=$HUB_URL
|
HUB_URL=$HUB_URL
|
||||||
EOF
|
EOF
|
||||||
chmod 640 "$AGENT_DIR/env"
|
chmod 640 "$AGENT_DIR/env"
|
||||||
chown root:beszel "$AGENT_DIR/env"
|
chown root:beszel "$AGENT_DIR/env"
|
||||||
|
else
|
||||||
|
echo "FreeBSD environment file already exists. Skipping creation."
|
||||||
|
fi
|
||||||
|
|
||||||
# Create the rc service file
|
# Create the rc service file if it doesn't exist
|
||||||
generate_freebsd_rc_service > /usr/local/etc/rc.d/beszel-agent
|
if [ ! -f /usr/local/etc/rc.d/beszel-agent ]; then
|
||||||
|
echo "Creating FreeBSD rc service..."
|
||||||
|
generate_freebsd_rc_service > /usr/local/etc/rc.d/beszel-agent
|
||||||
|
# Set proper permissions for the rc script
|
||||||
|
chmod 755 /usr/local/etc/rc.d/beszel-agent
|
||||||
|
else
|
||||||
|
echo "FreeBSD rc service file already exists. Skipping creation."
|
||||||
|
fi
|
||||||
|
|
||||||
# Set proper permissions for the rc script
|
|
||||||
chmod 755 /usr/local/etc/rc.d/beszel-agent
|
|
||||||
|
|
||||||
# Enable and start the service
|
# Enable and start the service
|
||||||
echo "Enabling and starting the agent service..."
|
echo "Enabling and starting the agent service..."
|
||||||
sysrc beszel_agent_enable="YES"
|
sysrc beszel_agent_enable="YES"
|
||||||
@@ -905,12 +942,13 @@ EOF
|
|||||||
|
|
||||||
else
|
else
|
||||||
# Original systemd service installation code
|
# Original systemd service installation code
|
||||||
echo "Creating the systemd service for the agent..."
|
if [ ! -f /etc/systemd/system/beszel-agent.service ]; then
|
||||||
|
echo "Creating the systemd service for the agent..."
|
||||||
|
|
||||||
# Detect NVIDIA devices and grant device permissions
|
# Detect NVIDIA devices and grant device permissions
|
||||||
NVIDIA_DEVICES=$(detect_nvidia_devices)
|
NVIDIA_DEVICES=$(detect_nvidia_devices)
|
||||||
|
|
||||||
cat >/etc/systemd/system/beszel-agent.service <<EOF
|
cat >/etc/systemd/system/beszel-agent.service <<EOF
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Beszel Agent Service
|
Description=Beszel Agent Service
|
||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
@@ -944,12 +982,15 @@ $(if [ -n "$NVIDIA_DEVICES" ]; then printf "%b" "# NVIDIA device permissions\n${
|
|||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
EOF
|
EOF
|
||||||
|
else
|
||||||
|
echo "Systemd service file already exists. Skipping creation."
|
||||||
|
fi
|
||||||
|
|
||||||
# Load and start the service
|
# Load and start the service
|
||||||
printf "\nLoading and starting the agent service...\n"
|
printf "\nLoading and starting the agent service...\n"
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable beszel-agent.service
|
systemctl enable beszel-agent.service
|
||||||
systemctl start beszel-agent.service
|
systemctl restart beszel-agent.service
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user