mirror of
https://github.com/henrygd/beszel.git
synced 2026-04-04 20:11:50 +02:00
Compare commits
1 Commits
f7618ed6b0
...
ssr-system
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
605e3f7e9d |
6
.github/workflows/vulncheck.yml
vendored
6
.github/workflows/vulncheck.yml
vendored
@@ -15,7 +15,7 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
vulncheck:
|
vulncheck:
|
||||||
name: VulnCheck
|
name: Analysis
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
@@ -23,8 +23,8 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.25.x
|
go-version: 1.24.x
|
||||||
# cached: false
|
cached: false
|
||||||
- name: Get official govulncheck
|
- name: Get official govulncheck
|
||||||
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module beszel
|
module beszel
|
||||||
|
|
||||||
go 1.25.1
|
go 1.24.4
|
||||||
|
|
||||||
// lock shoutrrr to specific version to allow review before updating
|
// lock shoutrrr to specific version to allow review before updating
|
||||||
replace github.com/nicholas-fedor/shoutrrr => github.com/nicholas-fedor/shoutrrr v0.8.8
|
replace github.com/nicholas-fedor/shoutrrr => github.com/nicholas-fedor/shoutrrr v0.8.8
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ var supportsTitle = map[string]struct{}{
|
|||||||
func NewAlertManager(app hubLike) *AlertManager {
|
func NewAlertManager(app hubLike) *AlertManager {
|
||||||
am := &AlertManager{
|
am := &AlertManager{
|
||||||
hub: app,
|
hub: app,
|
||||||
alertQueue: make(chan alertTask, 5),
|
alertQueue: make(chan alertTask),
|
||||||
stopChan: make(chan struct{}),
|
stopChan: make(chan struct{}),
|
||||||
}
|
}
|
||||||
am.bindEvents()
|
am.bindEvents()
|
||||||
|
|||||||
@@ -42,10 +42,21 @@ func updateHistoryOnAlertUpdate(e *core.RecordEvent) error {
|
|||||||
|
|
||||||
// resolveAlertHistoryRecord sets the resolved field to the current time
|
// resolveAlertHistoryRecord sets the resolved field to the current time
|
||||||
func resolveAlertHistoryRecord(app core.App, alertRecordID string) error {
|
func resolveAlertHistoryRecord(app core.App, alertRecordID string) error {
|
||||||
alertHistoryRecord, err := app.FindFirstRecordByFilter("alerts_history", "alert_id={:alert_id} && resolved=null", dbx.Params{"alert_id": alertRecordID})
|
alertHistoryRecords, err := app.FindRecordsByFilter(
|
||||||
if err != nil || alertHistoryRecord == nil {
|
"alerts_history",
|
||||||
|
"alert_id={:alert_id} && resolved=null",
|
||||||
|
"-created",
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
dbx.Params{"alert_id": alertRecordID},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if len(alertHistoryRecords) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
alertHistoryRecord := alertHistoryRecords[0] // there should be only one record
|
||||||
alertHistoryRecord.Set("resolved", time.Now().UTC())
|
alertHistoryRecord.Set("resolved", time.Now().UTC())
|
||||||
err = app.Save(alertHistoryRecord)
|
err = app.Save(alertHistoryRecord)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"testing/synctest"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
beszelTests "beszel/internal/tests"
|
beszelTests "beszel/internal/tests"
|
||||||
|
|
||||||
@@ -65,14 +63,14 @@ func TestUserAlertsApi(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
scenarios := []beszelTests.ApiScenario{
|
scenarios := []beszelTests.ApiScenario{
|
||||||
// {
|
{
|
||||||
// Name: "GET not implemented - returns index",
|
Name: "GET not implemented - returns index",
|
||||||
// Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
// URL: "/api/beszel/user-alerts",
|
URL: "/api/beszel/user-alerts",
|
||||||
// ExpectedStatus: 200,
|
ExpectedStatus: 200,
|
||||||
// ExpectedContent: []string{"<html ", "globalThis.BESZEL"},
|
ExpectedContent: []string{"<html ", "globalThis.BESZEL"},
|
||||||
// TestAppFactory: testAppFactory,
|
TestAppFactory: testAppFactory,
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
Name: "POST no auth",
|
Name: "POST no auth",
|
||||||
Method: http.MethodPost,
|
Method: http.MethodPost,
|
||||||
@@ -368,237 +366,3 @@ func TestUserAlertsApi(t *testing.T) {
|
|||||||
scenario.Test(t)
|
scenario.Test(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getHubWithUser(t *testing.T) (*beszelTests.TestHub, *core.Record) {
|
|
||||||
hub, err := beszelTests.NewTestHub(t.TempDir())
|
|
||||||
assert.NoError(t, err)
|
|
||||||
hub.StartHub()
|
|
||||||
|
|
||||||
// Manually initialize the system manager to bind event hooks
|
|
||||||
err = hub.GetSystemManager().Initialize()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
// Create a test user
|
|
||||||
user, err := beszelTests.CreateUser(hub, "test@example.com", "password")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
// Create user settings for the test user (required for alert notifications)
|
|
||||||
userSettingsData := map[string]any{
|
|
||||||
"user": user.Id,
|
|
||||||
"settings": `{"emails":[test@example.com],"webhooks":[]}`,
|
|
||||||
}
|
|
||||||
_, err = beszelTests.CreateRecord(hub, "user_settings", userSettingsData)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
return hub, user
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStatusAlerts(t *testing.T) {
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
|
||||||
hub, user := getHubWithUser(t)
|
|
||||||
defer hub.Cleanup()
|
|
||||||
|
|
||||||
systems, err := beszelTests.CreateSystems(hub, 4, user.Id, "paused")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
var alerts []*core.Record
|
|
||||||
for i, system := range systems {
|
|
||||||
alert, err := beszelTests.CreateRecord(hub, "alerts", map[string]any{
|
|
||||||
"name": "Status",
|
|
||||||
"system": system.Id,
|
|
||||||
"user": user.Id,
|
|
||||||
"min": i + 1,
|
|
||||||
})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
alerts = append(alerts, alert)
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
|
|
||||||
for _, alert := range alerts {
|
|
||||||
assert.False(t, alert.GetBool("triggered"), "Alert should not be triggered immediately")
|
|
||||||
}
|
|
||||||
if hub.TestMailer.TotalSend() != 0 {
|
|
||||||
assert.Zero(t, hub.TestMailer.TotalSend(), "Expected 0 messages, got %d", hub.TestMailer.TotalSend())
|
|
||||||
}
|
|
||||||
for _, system := range systems {
|
|
||||||
assert.EqualValues(t, "paused", system.GetString("status"), "System should be paused")
|
|
||||||
}
|
|
||||||
for _, system := range systems {
|
|
||||||
system.Set("status", "up")
|
|
||||||
err = hub.SaveNoValidate(system)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
assert.EqualValues(t, 0, hub.GetPendingAlertsCount(), "should have 0 alerts in the pendingAlerts map")
|
|
||||||
for _, system := range systems {
|
|
||||||
system.Set("status", "down")
|
|
||||||
err = hub.SaveNoValidate(system)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
// after 30 seconds, should have 4 alerts in the pendingAlerts map, no triggered alerts
|
|
||||||
time.Sleep(time.Second * 30)
|
|
||||||
assert.EqualValues(t, 4, hub.GetPendingAlertsCount(), "should have 4 alerts in the pendingAlerts map")
|
|
||||||
triggeredCount, err := hub.CountRecords("alerts", dbx.HashExp{"triggered": true})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.EqualValues(t, 0, triggeredCount, "should have 0 alert triggered")
|
|
||||||
assert.EqualValues(t, 0, hub.TestMailer.TotalSend(), "should have 0 messages sent")
|
|
||||||
// after 1:30 seconds, should have 1 triggered alert and 3 pending alerts
|
|
||||||
time.Sleep(time.Second * 60)
|
|
||||||
assert.EqualValues(t, 3, hub.GetPendingAlertsCount(), "should have 3 alerts in the pendingAlerts map")
|
|
||||||
triggeredCount, err = hub.CountRecords("alerts", dbx.HashExp{"triggered": true})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.EqualValues(t, 1, triggeredCount, "should have 1 alert triggered")
|
|
||||||
assert.EqualValues(t, 1, hub.TestMailer.TotalSend(), "should have 1 messages sent")
|
|
||||||
// after 2:30 seconds, should have 2 triggered alerts and 2 pending alerts
|
|
||||||
time.Sleep(time.Second * 60)
|
|
||||||
assert.EqualValues(t, 2, hub.GetPendingAlertsCount(), "should have 2 alerts in the pendingAlerts map")
|
|
||||||
triggeredCount, err = hub.CountRecords("alerts", dbx.HashExp{"triggered": true})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.EqualValues(t, 2, triggeredCount, "should have 2 alert triggered")
|
|
||||||
assert.EqualValues(t, 2, hub.TestMailer.TotalSend(), "should have 2 messages sent")
|
|
||||||
// now we will bring the remaning systems back up
|
|
||||||
for _, system := range systems {
|
|
||||||
system.Set("status", "up")
|
|
||||||
err = hub.SaveNoValidate(system)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
// should have 0 alerts in the pendingAlerts map and 0 alerts triggered
|
|
||||||
assert.EqualValues(t, 0, hub.GetPendingAlertsCount(), "should have 0 alerts in the pendingAlerts map")
|
|
||||||
triggeredCount, err = hub.CountRecords("alerts", dbx.HashExp{"triggered": true})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Zero(t, triggeredCount, "should have 0 alert triggered")
|
|
||||||
// 4 messages sent, 2 down alerts and 2 up alerts for first 2 systems
|
|
||||||
assert.EqualValues(t, 4, hub.TestMailer.TotalSend(), "should have 4 messages sent")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAlertsHistory(t *testing.T) {
|
|
||||||
synctest.Test(t, func(t *testing.T) {
|
|
||||||
hub, user := getHubWithUser(t)
|
|
||||||
defer hub.Cleanup()
|
|
||||||
|
|
||||||
// Create systems and alerts
|
|
||||||
systems, err := beszelTests.CreateSystems(hub, 1, user.Id, "up")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
system := systems[0]
|
|
||||||
|
|
||||||
alert, err := beszelTests.CreateRecord(hub, "alerts", map[string]any{
|
|
||||||
"name": "Status",
|
|
||||||
"system": system.Id,
|
|
||||||
"user": user.Id,
|
|
||||||
"min": 1,
|
|
||||||
})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
// Initially, no alert history records should exist
|
|
||||||
initialHistoryCount, err := hub.CountRecords("alerts_history", nil)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Zero(t, initialHistoryCount, "Should have 0 alert history records initially")
|
|
||||||
|
|
||||||
// Set system to up initially
|
|
||||||
system.Set("status", "up")
|
|
||||||
err = hub.SaveNoValidate(system)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
|
|
||||||
// Set system to down to trigger alert
|
|
||||||
system.Set("status", "down")
|
|
||||||
err = hub.SaveNoValidate(system)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
// Wait for alert to trigger (after the downtime delay)
|
|
||||||
// With 1 minute delay, we need to wait at least 1 minute + some buffer
|
|
||||||
time.Sleep(time.Second * 75)
|
|
||||||
|
|
||||||
// Check that alert is triggered
|
|
||||||
triggeredCount, err := hub.CountRecords("alerts", dbx.HashExp{"triggered": true, "id": alert.Id})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.EqualValues(t, 1, triggeredCount, "Alert should be triggered")
|
|
||||||
|
|
||||||
// Check that alert history record was created
|
|
||||||
historyCount, err := hub.CountRecords("alerts_history", dbx.HashExp{"alert_id": alert.Id})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.EqualValues(t, 1, historyCount, "Should have 1 alert history record for triggered alert")
|
|
||||||
|
|
||||||
// Get the alert history record and verify it's not resolved immediately
|
|
||||||
historyRecord, err := hub.FindFirstRecordByFilter("alerts_history", "alert_id={:alert_id}", dbx.Params{"alert_id": alert.Id})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotNil(t, historyRecord, "Alert history record should exist")
|
|
||||||
assert.Equal(t, alert.Id, historyRecord.GetString("alert_id"), "Alert history should reference correct alert")
|
|
||||||
assert.Equal(t, system.Id, historyRecord.GetString("system"), "Alert history should reference correct system")
|
|
||||||
assert.Equal(t, "Status", historyRecord.GetString("name"), "Alert history should have correct name")
|
|
||||||
|
|
||||||
// The alert history might be resolved immediately in some cases, so let's check the alert's triggered status
|
|
||||||
alertRecord, err := hub.FindFirstRecordByFilter("alerts", "id={:id}", dbx.Params{"id": alert.Id})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.True(t, alertRecord.GetBool("triggered"), "Alert should still be triggered when checking history")
|
|
||||||
|
|
||||||
// Now resolve the alert by setting system back to up
|
|
||||||
system.Set("status", "up")
|
|
||||||
err = hub.SaveNoValidate(system)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
time.Sleep(200 * time.Millisecond)
|
|
||||||
|
|
||||||
// Check that alert is no longer triggered
|
|
||||||
triggeredCount, err = hub.CountRecords("alerts", dbx.HashExp{"triggered": true, "id": alert.Id})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Zero(t, triggeredCount, "Alert should not be triggered after system is back up")
|
|
||||||
|
|
||||||
// Check that alert history record is now resolved
|
|
||||||
historyRecord, err = hub.FindFirstRecordByFilter("alerts_history", "alert_id={:alert_id}", dbx.Params{"alert_id": alert.Id})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotNil(t, historyRecord, "Alert history record should still exist")
|
|
||||||
assert.NotNil(t, historyRecord.Get("resolved"), "Alert history should be resolved")
|
|
||||||
|
|
||||||
// Test deleting a triggered alert resolves its history
|
|
||||||
// Create another system and alert
|
|
||||||
systems2, err := beszelTests.CreateSystems(hub, 1, user.Id, "up")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
system2 := systems2[0]
|
|
||||||
system2.Set("name", "test-system-2") // Rename for clarity
|
|
||||||
err = hub.SaveNoValidate(system2)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
alert2, err := beszelTests.CreateRecord(hub, "alerts", map[string]any{
|
|
||||||
"name": "Status",
|
|
||||||
"system": system2.Id,
|
|
||||||
"user": user.Id,
|
|
||||||
"min": 1,
|
|
||||||
})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
// Set system2 to down to trigger alert
|
|
||||||
system2.Set("status", "down")
|
|
||||||
err = hub.SaveNoValidate(system2)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
// Wait for alert to trigger
|
|
||||||
time.Sleep(time.Second * 75)
|
|
||||||
|
|
||||||
// Verify alert is triggered and history record exists
|
|
||||||
triggeredCount, err = hub.CountRecords("alerts", dbx.HashExp{"triggered": true, "id": alert2.Id})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.EqualValues(t, 1, triggeredCount, "Second alert should be triggered")
|
|
||||||
|
|
||||||
historyCount, err = hub.CountRecords("alerts_history", dbx.HashExp{"alert_id": alert2.Id})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.EqualValues(t, 1, historyCount, "Should have 1 alert history record for second alert")
|
|
||||||
|
|
||||||
// Delete the triggered alert
|
|
||||||
err = hub.Delete(alert2)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
// Check that alert history record is resolved after deletion
|
|
||||||
historyRecord2, err := hub.FindFirstRecordByFilter("alerts_history", "alert_id={:alert_id}", dbx.Params{"alert_id": alert2.Id})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotNil(t, historyRecord2, "Alert history record should still exist after alert deletion")
|
|
||||||
assert.NotNil(t, historyRecord2.Get("resolved"), "Alert history should be resolved after alert deletion")
|
|
||||||
|
|
||||||
// Verify total history count is correct (2 records total)
|
|
||||||
totalHistoryCount, err := hub.CountRecords("alerts_history", nil)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.EqualValues(t, 2, totalHistoryCount, "Should have 2 total alert history records")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
package alerts
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pocketbase/pocketbase/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (am *AlertManager) GetAlertManager() *AlertManager {
|
|
||||||
return am
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *AlertManager) GetPendingAlerts() *sync.Map {
|
|
||||||
return &am.pendingAlerts
|
|
||||||
}
|
|
||||||
|
|
||||||
func (am *AlertManager) GetPendingAlertsCount() int {
|
|
||||||
count := 0
|
|
||||||
am.pendingAlerts.Range(func(key, value any) bool {
|
|
||||||
count++
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessPendingAlerts manually processes all expired alerts (for testing)
|
|
||||||
func (am *AlertManager) ProcessPendingAlerts() ([]*core.Record, error) {
|
|
||||||
now := time.Now()
|
|
||||||
var lastErr error
|
|
||||||
var processedAlerts []*core.Record
|
|
||||||
am.pendingAlerts.Range(func(key, value any) bool {
|
|
||||||
info := value.(*alertInfo)
|
|
||||||
if now.After(info.expireTime) {
|
|
||||||
// Downtime delay has passed, process alert
|
|
||||||
if err := am.sendStatusAlert("down", info.systemName, info.alertRecord); err != nil {
|
|
||||||
lastErr = err
|
|
||||||
}
|
|
||||||
processedAlerts = append(processedAlerts, info.alertRecord)
|
|
||||||
am.pendingAlerts.Delete(key)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
return processedAlerts, lastErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// ForceExpirePendingAlerts sets all pending alerts to expire immediately (for testing)
|
|
||||||
func (am *AlertManager) ForceExpirePendingAlerts() {
|
|
||||||
now := time.Now()
|
|
||||||
am.pendingAlerts.Range(func(key, value any) bool {
|
|
||||||
info := value.(*alertInfo)
|
|
||||||
info.expireTime = now.Add(-time.Second) // Set to 1 second ago
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/pocketbase/dbx"
|
||||||
"github.com/pocketbase/pocketbase"
|
"github.com/pocketbase/pocketbase"
|
||||||
"github.com/pocketbase/pocketbase/apis"
|
"github.com/pocketbase/pocketbase/apis"
|
||||||
"github.com/pocketbase/pocketbase/core"
|
"github.com/pocketbase/pocketbase/core"
|
||||||
@@ -299,3 +300,30 @@ func (h *Hub) MakeLink(parts ...string) string {
|
|||||||
}
|
}
|
||||||
return base
|
return base
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SystemInfo struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Id string `json:"id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Port uint16 `json:"port"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
Info string `json:"info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Hub) getUserSystemsFromRequest(req *http.Request) ([]SystemInfo, error) {
|
||||||
|
systems := []SystemInfo{}
|
||||||
|
token, err := req.Cookie("beszauth")
|
||||||
|
if err != nil {
|
||||||
|
return systems, err
|
||||||
|
}
|
||||||
|
if token.Value != "" {
|
||||||
|
user, err := h.FindAuthRecordByToken(token.Value)
|
||||||
|
if err != nil {
|
||||||
|
return systems, err
|
||||||
|
}
|
||||||
|
h.DB().NewQuery("SELECT s.id, s.info, s.status, s.name, s.port, s.host FROM systems s JOIN json_each(s.users) AS je WHERE je.value = {:user_id}").Bind(dbx.Params{
|
||||||
|
"user_id": user.Id,
|
||||||
|
}).All(&systems)
|
||||||
|
}
|
||||||
|
return systems, err
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package hub
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"beszel"
|
"beszel"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
@@ -15,29 +16,36 @@ import (
|
|||||||
"github.com/pocketbase/pocketbase/core"
|
"github.com/pocketbase/pocketbase/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Wraps http.RoundTripper to modify dev proxy HTML responses
|
// responseModifier wraps an http.RoundTripper to modify HTML responses
|
||||||
type responseModifier struct {
|
type responseModifier struct {
|
||||||
transport http.RoundTripper
|
transport http.RoundTripper
|
||||||
hub *Hub
|
hub *Hub
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RoundTrip implements http.RoundTripper interface with response modification
|
||||||
func (rm *responseModifier) RoundTrip(req *http.Request) (*http.Response, error) {
|
func (rm *responseModifier) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
resp, err := rm.transport.RoundTrip(req)
|
resp, err := rm.transport.RoundTrip(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only modify HTML responses
|
// Only modify HTML responses
|
||||||
contentType := resp.Header.Get("Content-Type")
|
contentType := resp.Header.Get("Content-Type")
|
||||||
if !strings.Contains(contentType, "text/html") {
|
if !strings.Contains(contentType, "text/html") {
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read the response body
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
resp.Body.Close()
|
resp.Body.Close()
|
||||||
|
|
||||||
|
// Modify the HTML content here
|
||||||
|
modifiedBody := rm.modifyHTML(string(body), req)
|
||||||
|
|
||||||
// Create a new response with the modified body
|
// Create a new response with the modified body
|
||||||
modifiedBody := rm.modifyHTML(string(body))
|
|
||||||
resp.Body = io.NopCloser(strings.NewReader(modifiedBody))
|
resp.Body = io.NopCloser(strings.NewReader(modifiedBody))
|
||||||
resp.ContentLength = int64(len(modifiedBody))
|
resp.ContentLength = int64(len(modifiedBody))
|
||||||
resp.Header.Set("Content-Length", fmt.Sprintf("%d", len(modifiedBody)))
|
resp.Header.Set("Content-Length", fmt.Sprintf("%d", len(modifiedBody)))
|
||||||
@@ -45,7 +53,8 @@ func (rm *responseModifier) RoundTrip(req *http.Request) (*http.Response, error)
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rm *responseModifier) modifyHTML(html string) string {
|
// modifyHTML applies modifications to HTML content
|
||||||
|
func (rm *responseModifier) modifyHTML(html string, req *http.Request) string {
|
||||||
parsedURL, err := url.Parse(rm.hub.appURL)
|
parsedURL, err := url.Parse(rm.hub.appURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return html
|
return html
|
||||||
@@ -54,7 +63,19 @@ func (rm *responseModifier) modifyHTML(html string) string {
|
|||||||
basePath := strings.TrimSuffix(parsedURL.Path, "/") + "/"
|
basePath := strings.TrimSuffix(parsedURL.Path, "/") + "/"
|
||||||
html = strings.ReplaceAll(html, "./", basePath)
|
html = strings.ReplaceAll(html, "./", basePath)
|
||||||
html = strings.Replace(html, "{{V}}", beszel.Version, 1)
|
html = strings.Replace(html, "{{V}}", beszel.Version, 1)
|
||||||
|
slog.Info("modifying HTML", "appURL", rm.hub.appURL)
|
||||||
html = strings.Replace(html, "{{HUB_URL}}", rm.hub.appURL, 1)
|
html = strings.Replace(html, "{{HUB_URL}}", rm.hub.appURL, 1)
|
||||||
|
|
||||||
|
systems, err := rm.hub.getUserSystemsFromRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
systemsJson, err := json.Marshal(systems)
|
||||||
|
if err != nil {
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
html = strings.Replace(html, "'{SYSTEMS}'", string(systemsJson), 1)
|
||||||
|
|
||||||
return html
|
return html
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,6 +87,7 @@ func (h *Hub) startServer(se *core.ServeEvent) error {
|
|||||||
Host: "localhost:5173",
|
Host: "localhost:5173",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Set up custom transport with response modification
|
||||||
proxy.Transport = &responseModifier{
|
proxy.Transport = &responseModifier{
|
||||||
transport: http.DefaultTransport,
|
transport: http.DefaultTransport,
|
||||||
hub: h,
|
hub: h,
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ package hub
|
|||||||
import (
|
import (
|
||||||
"beszel"
|
"beszel"
|
||||||
"beszel/site"
|
"beszel/site"
|
||||||
|
"encoding/json"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -45,6 +47,15 @@ func (h *Hub) startServer(se *core.ServeEvent) error {
|
|||||||
e.Response.Header().Del("X-Frame-Options")
|
e.Response.Header().Del("X-Frame-Options")
|
||||||
e.Response.Header().Set("Content-Security-Policy", csp)
|
e.Response.Header().Set("Content-Security-Policy", csp)
|
||||||
}
|
}
|
||||||
|
systems, err := h.getUserSystemsFromRequest(e.Request)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("error getting user systems", "error", err)
|
||||||
|
}
|
||||||
|
systemsJson, err := json.Marshal(systems)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("error marshalling user systems", "error", err)
|
||||||
|
}
|
||||||
|
html = strings.Replace(html, "'{SYSTEMS}'", string(systemsJson), 1)
|
||||||
return e.HTML(http.StatusOK, html)
|
return e.HTML(http.StatusOK, html)
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -100,10 +100,3 @@ func (sm *SystemManager) SetSystemStatusInDB(systemID string, status string) boo
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// TESTING ONLY: RemoveAllSystems removes all systems from the store
|
|
||||||
func (sm *SystemManager) RemoveAllSystems() {
|
|
||||||
for _, system := range sm.systems.GetAll() {
|
|
||||||
sm.RemoveSystem(system.Id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -96,31 +96,3 @@ func ClearCollection(t testing.TB, app core.App, collectionName string) error {
|
|||||||
assert.EqualValues(t, recordCount, 0, "should have 0 records after clearing")
|
assert.EqualValues(t, recordCount, 0, "should have 0 records after clearing")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *TestHub) Cleanup() {
|
|
||||||
h.GetAlertManager().StopWorker()
|
|
||||||
h.GetSystemManager().RemoveAllSystems()
|
|
||||||
h.TestApp.Cleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateSystems(app core.App, count int, userId string, status string) ([]*core.Record, error) {
|
|
||||||
systems := make([]*core.Record, 0, count)
|
|
||||||
for i := range count {
|
|
||||||
system, err := CreateRecord(app, "systems", map[string]any{
|
|
||||||
"name": fmt.Sprintf("test-system-%d", i),
|
|
||||||
"host": fmt.Sprintf("127.0.0.%d", i),
|
|
||||||
"port": "33914",
|
|
||||||
"users": []string{userId},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
system.Set("status", status)
|
|
||||||
err = app.SaveNoValidate(system)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
systems = append(systems, system)
|
|
||||||
}
|
|
||||||
return systems, nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,7 +10,8 @@
|
|||||||
globalThis.BESZEL = {
|
globalThis.BESZEL = {
|
||||||
BASE_PATH: "%BASE_URL%",
|
BASE_PATH: "%BASE_URL%",
|
||||||
HUB_VERSION: "{{V}}",
|
HUB_VERSION: "{{V}}",
|
||||||
HUB_URL: "{{HUB_URL}}"
|
HUB_URL: "{{HUB_URL}}",
|
||||||
|
SYSTEMS: '{SYSTEMS}'
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -121,7 +121,6 @@ export default memo(function CommandPalette({ open, setOpen }: { open: boolean;
|
|||||||
{SettingsShortcut}
|
{SettingsShortcut}
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
<CommandItem
|
<CommandItem
|
||||||
keywords={[t`Universal token`]}
|
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
navigate(getPagePath($router, "settings", { name: "tokens" }))
|
navigate(getPagePath($router, "settings", { name: "tokens" }))
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
|
|||||||
@@ -12,6 +12,13 @@ export const pb = new PocketBase(basePath)
|
|||||||
export const isAdmin = () => pb.authStore.record?.role === "admin"
|
export const isAdmin = () => pb.authStore.record?.role === "admin"
|
||||||
export const isReadOnlyUser = () => pb.authStore.record?.role === "readonly"
|
export const isReadOnlyUser = () => pb.authStore.record?.role === "readonly"
|
||||||
|
|
||||||
|
export const updateCookieToken = () => {
|
||||||
|
console.log("setting token", pb.authStore.token)
|
||||||
|
document.cookie = `beszauth=${pb.authStore.token}; path=/; expires=${new Date(
|
||||||
|
Date.now() + 7 * 24 * 60 * 60 * 1000
|
||||||
|
).toString()}`
|
||||||
|
}
|
||||||
|
|
||||||
export const verifyAuth = () => {
|
export const verifyAuth = () => {
|
||||||
pb.collection("users")
|
pb.collection("users")
|
||||||
.authRefresh()
|
.authRefresh()
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const $downSystems = map<Record<string, SystemRecord>>({})
|
|||||||
/** Map of paused systems by id */
|
/** Map of paused systems by id */
|
||||||
export const $pausedSystems = map<Record<string, SystemRecord>>({})
|
export const $pausedSystems = map<Record<string, SystemRecord>>({})
|
||||||
/** List of all system records */
|
/** List of all system records */
|
||||||
export const $systems: ReadableAtom<SystemRecord[]> = computed($allSystemsById, Object.values)
|
export const $systems: ReadableAtom<SystemRecord[]> = computed($allSystemsByName, Object.values)
|
||||||
|
|
||||||
/** Map of alert records by system id and alert name */
|
/** Map of alert records by system id and alert name */
|
||||||
export const $alerts = map<AlertMap>({})
|
export const $alerts = map<AlertMap>({})
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export function init() {
|
|||||||
initialized = true
|
initialized = true
|
||||||
|
|
||||||
// sync system stores on change
|
// sync system stores on change
|
||||||
$allSystemsById.listen((newSystems, oldSystems, changedKey) => {
|
$allSystemsByName.listen((newSystems, oldSystems, changedKey) => {
|
||||||
const oldSystem = oldSystems[changedKey]
|
const oldSystem = oldSystems[changedKey]
|
||||||
const newSystem = newSystems[changedKey]
|
const newSystem = newSystems[changedKey]
|
||||||
|
|
||||||
@@ -59,10 +59,6 @@ export function init() {
|
|||||||
$pausedSystems.setKey(newSystem.id, newSystem)
|
$pausedSystems.setKey(newSystem.id, newSystem)
|
||||||
removeFromStore(newSystem, $upSystems)
|
removeFromStore(newSystem, $upSystems)
|
||||||
removeFromStore(newSystem, $downSystems)
|
removeFromStore(newSystem, $downSystems)
|
||||||
} else if (newStatus === SystemStatus.Pending) {
|
|
||||||
removeFromStore(newSystem, $upSystems)
|
|
||||||
removeFromStore(newSystem, $downSystems)
|
|
||||||
removeFromStore(newSystem, $pausedSystems)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// run things that need to be done when systems change
|
// run things that need to be done when systems change
|
||||||
@@ -104,22 +100,13 @@ async function fetchSystems(): Promise<SystemRecord[]> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store management functions
|
||||||
/** Add system to both name and ID stores */
|
/** Add system to both name and ID stores */
|
||||||
export function add(system: SystemRecord) {
|
export function add(system: SystemRecord) {
|
||||||
$allSystemsByName.setKey(system.name, system)
|
$allSystemsByName.setKey(system.name, system)
|
||||||
$allSystemsById.setKey(system.id, system)
|
$allSystemsById.setKey(system.id, system)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Update system in stores */
|
|
||||||
export function update(system: SystemRecord) {
|
|
||||||
// if name changed, make sure old name is removed from the name store
|
|
||||||
const oldName = $allSystemsById.get()[system.id]?.name
|
|
||||||
if (oldName !== system.name) {
|
|
||||||
$allSystemsByName.setKey(oldName, undefined as any)
|
|
||||||
}
|
|
||||||
add(system)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Remove system from stores */
|
/** Remove system from stores */
|
||||||
export function remove(system: SystemRecord) {
|
export function remove(system: SystemRecord) {
|
||||||
removeFromStore(system, $allSystemsByName)
|
removeFromStore(system, $allSystemsByName)
|
||||||
@@ -138,7 +125,7 @@ function removeFromStore(system: SystemRecord, store: PreinitializedMapStore<Rec
|
|||||||
/** Action functions for subscription */
|
/** Action functions for subscription */
|
||||||
const actionFns: Record<string, (system: SystemRecord) => void> = {
|
const actionFns: Record<string, (system: SystemRecord) => void> = {
|
||||||
create: add,
|
create: add,
|
||||||
update: update,
|
update: add,
|
||||||
delete: remove,
|
delete: remove,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +141,13 @@ export async function subscribe() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Refresh all systems with latest data from the hub */
|
/** Refresh all systems with latest data from the hub */
|
||||||
export async function refresh() {
|
export async function refresh(records: SystemRecord[] = []) {
|
||||||
|
if (records.length) {
|
||||||
|
for (const record of records) {
|
||||||
|
add(record)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const records = await fetchSystems()
|
const records = await fetchSystems()
|
||||||
if (!records.length) {
|
if (!records.length) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import ReactDOM from "react-dom/client"
|
|||||||
import { ThemeProvider } from "./components/theme-provider.tsx"
|
import { ThemeProvider } from "./components/theme-provider.tsx"
|
||||||
import { DirectionProvider } from "@radix-ui/react-direction"
|
import { DirectionProvider } from "@radix-ui/react-direction"
|
||||||
import { $authenticated, $publicKey, $copyContent, $direction } from "./lib/stores.ts"
|
import { $authenticated, $publicKey, $copyContent, $direction } from "./lib/stores.ts"
|
||||||
import { pb, updateUserSettings } from "./lib/api.ts"
|
import { pb, updateUserSettings, updateCookieToken } from "./lib/api.ts"
|
||||||
import * as systemsManager from "./lib/systemsManager.ts"
|
import * as systemsManager from "./lib/systemsManager.ts"
|
||||||
import { useStore } from "@nanostores/react"
|
import { useStore } from "@nanostores/react"
|
||||||
import { Toaster } from "./components/ui/toaster.tsx"
|
import { Toaster } from "./components/ui/toaster.tsx"
|
||||||
@@ -27,8 +27,10 @@ const App = memo(() => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// change auth store on auth change
|
// change auth store on auth change
|
||||||
|
updateCookieToken()
|
||||||
pb.authStore.onChange(() => {
|
pb.authStore.onChange(() => {
|
||||||
$authenticated.set(pb.authStore.isValid)
|
$authenticated.set(pb.authStore.isValid)
|
||||||
|
updateCookieToken()
|
||||||
})
|
})
|
||||||
// get version / public key
|
// get version / public key
|
||||||
pb.send("/api/beszel/getkey", {}).then((data) => {
|
pb.send("/api/beszel/getkey", {}).then((data) => {
|
||||||
@@ -36,11 +38,17 @@ const App = memo(() => {
|
|||||||
})
|
})
|
||||||
// get user settings
|
// get user settings
|
||||||
updateUserSettings()
|
updateUserSettings()
|
||||||
|
const startingSystems = globalThis.BESZEL.SYSTEMS
|
||||||
|
for (const system of startingSystems) {
|
||||||
|
// if (typeof system.info === "string") {
|
||||||
|
system.info = JSON.parse(system.info as unknown as string)
|
||||||
|
// }
|
||||||
|
}
|
||||||
// need to get system list before alerts
|
// need to get system list before alerts
|
||||||
systemsManager.init()
|
systemsManager.init()
|
||||||
systemsManager
|
systemsManager
|
||||||
// get current systems list
|
// get current systems list
|
||||||
.refresh()
|
.refresh(startingSystems)
|
||||||
// subscribe to new system updates
|
// subscribe to new system updates
|
||||||
.then(systemsManager.subscribe)
|
.then(systemsManager.subscribe)
|
||||||
// get current alerts
|
// get current alerts
|
||||||
@@ -51,6 +59,7 @@ const App = memo(() => {
|
|||||||
// updateFavicon("favicon.svg")
|
// updateFavicon("favicon.svg")
|
||||||
alertManager.unsubscribe()
|
alertManager.unsubscribe()
|
||||||
systemsManager.unsubscribe()
|
systemsManager.unsubscribe()
|
||||||
|
globalThis.BESZEL.SYSTEMS = []
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|||||||
2
beszel/site/src/types.d.ts
vendored
2
beszel/site/src/types.d.ts
vendored
@@ -7,6 +7,8 @@ declare global {
|
|||||||
BASE_PATH: string
|
BASE_PATH: string
|
||||||
HUB_VERSION: string
|
HUB_VERSION: string
|
||||||
HUB_URL: string
|
HUB_URL: string
|
||||||
|
/** initial list of systems */
|
||||||
|
SYSTEMS: SystemRecord[]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -216,11 +216,11 @@ if [ "$UNINSTALL" = true ]; then
|
|||||||
echo "Removing the OpenRC service files..."
|
echo "Removing the OpenRC service files..."
|
||||||
rm -f /etc/init.d/beszel-agent
|
rm -f /etc/init.d/beszel-agent
|
||||||
|
|
||||||
# Remove the daily update cron job if it exists
|
# Remove the update service if it exists
|
||||||
echo "Removing the daily update cron job..."
|
echo "Removing the daily update service..."
|
||||||
if crontab -u root -l 2>/dev/null | grep -q "beszel-agent.*update"; then
|
rc-service beszel-agent-update stop 2>/dev/null
|
||||||
crontab -u root -l 2>/dev/null | grep -v "beszel-agent.*update" | crontab -u root -
|
rc-update del beszel-agent-update default 2>/dev/null
|
||||||
fi
|
rm -f /etc/init.d/beszel-agent-update
|
||||||
|
|
||||||
# Remove log files
|
# Remove log files
|
||||||
echo "Removing log files..."
|
echo "Removing log files..."
|
||||||
@@ -321,9 +321,6 @@ if [ -z "$KEY" ]; then
|
|||||||
read KEY
|
read KEY
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Remove newlines from KEY
|
|
||||||
KEY=$(echo "$KEY" | tr -d '\n')
|
|
||||||
|
|
||||||
# TOKEN and HUB_URL are optional for backwards compatibility - no interactive prompts
|
# TOKEN and HUB_URL are optional for backwards compatibility - no interactive prompts
|
||||||
# They will be set as empty environment variables if not provided
|
# They will be set as empty environment variables if not provided
|
||||||
|
|
||||||
@@ -526,19 +523,35 @@ EOF
|
|||||||
elif [ "$AUTO_UPDATE_FLAG" = "false" ]; then
|
elif [ "$AUTO_UPDATE_FLAG" = "false" ]; then
|
||||||
AUTO_UPDATE="n"
|
AUTO_UPDATE="n"
|
||||||
else
|
else
|
||||||
printf "\nEnable automatic daily updates for beszel-agent? (y/n): "
|
printf "\nWould you like to enable automatic daily updates for beszel-agent? (y/n): "
|
||||||
read AUTO_UPDATE
|
read AUTO_UPDATE
|
||||||
fi
|
fi
|
||||||
case "$AUTO_UPDATE" in
|
case "$AUTO_UPDATE" in
|
||||||
[Yy]*)
|
[Yy]*)
|
||||||
echo "Setting up daily automatic updates for beszel-agent..."
|
echo "Setting up daily automatic updates for beszel-agent..."
|
||||||
|
|
||||||
# Create cron job to run beszel-agent update command daily at midnight
|
cat >/etc/init.d/beszel-agent-update <<EOF
|
||||||
if ! crontab -u root -l 2>/dev/null | grep -q "beszel-agent.*update"; then
|
#!/sbin/openrc-run
|
||||||
(crontab -u root -l 2>/dev/null; echo "12 0 * * * /opt/beszel-agent/beszel-agent update >/dev/null 2>&1") | crontab -u root -
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf "\nDaily updates have been enabled via cron job.\n"
|
name="beszel-agent-update"
|
||||||
|
description="Update beszel-agent if needed"
|
||||||
|
|
||||||
|
depend() {
|
||||||
|
need beszel-agent
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
ebegin "Checking for beszel-agent updates"
|
||||||
|
/opt/beszel-agent/beszel-agent update
|
||||||
|
eend $?
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x /etc/init.d/beszel-agent-update
|
||||||
|
rc-update add beszel-agent-update default
|
||||||
|
rc-service beszel-agent-update start
|
||||||
|
|
||||||
|
printf "\nAutomatic daily updates have been enabled.\n"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
@@ -599,7 +612,7 @@ EOF
|
|||||||
AUTO_UPDATE="n"
|
AUTO_UPDATE="n"
|
||||||
sleep 1 # give time for the service to start
|
sleep 1 # give time for the service to start
|
||||||
else
|
else
|
||||||
printf "\nEnable automatic daily updates for beszel-agent? (y/n): "
|
printf "\nWould you like to enable automatic daily updates for beszel-agent? (y/n): "
|
||||||
read AUTO_UPDATE
|
read AUTO_UPDATE
|
||||||
fi
|
fi
|
||||||
case "$AUTO_UPDATE" in
|
case "$AUTO_UPDATE" in
|
||||||
@@ -607,12 +620,12 @@ EOF
|
|||||||
echo "Setting up daily automatic updates for beszel-agent..."
|
echo "Setting up daily automatic updates for beszel-agent..."
|
||||||
|
|
||||||
cat >/etc/crontabs/beszel <<EOF
|
cat >/etc/crontabs/beszel <<EOF
|
||||||
12 0 * * * /etc/init.d/beszel-agent update
|
0 0 * * * /etc/init.d/beszel-agent update
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
/etc/init.d/cron restart
|
/etc/init.d/cron restart
|
||||||
|
|
||||||
printf "\nDaily updates have been enabled.\n"
|
printf "\nAutomatic daily updates have been enabled.\n"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
@@ -682,7 +695,7 @@ EOF
|
|||||||
AUTO_UPDATE="n"
|
AUTO_UPDATE="n"
|
||||||
sleep 1 # give time for the service to start
|
sleep 1 # give time for the service to start
|
||||||
else
|
else
|
||||||
printf "\nEnable automatic daily updates for beszel-agent? (y/n): "
|
printf "\nWould you like to enable automatic daily updates for beszel-agent? (y/n): "
|
||||||
read AUTO_UPDATE
|
read AUTO_UPDATE
|
||||||
fi
|
fi
|
||||||
case "$AUTO_UPDATE" in
|
case "$AUTO_UPDATE" in
|
||||||
@@ -717,7 +730,7 @@ EOF
|
|||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable --now beszel-agent-update.timer
|
systemctl enable --now beszel-agent-update.timer
|
||||||
|
|
||||||
printf "\nDaily updates have been enabled.\n"
|
printf "\nAutomatic daily updates have been enabled.\n"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user