From 8de2dee4e97a3d106b68ea1d252b71cf83c897c5 Mon Sep 17 00:00:00 2001 From: Henry Dollman Date: Mon, 7 Oct 2024 18:58:57 -0400 Subject: [PATCH] built-in agent --- beszel/internal/agent/agent.go | 8 +- beszel/internal/agent/server.go | 2 +- beszel/internal/hub/hub.go | 90 +++++++++++++------ .../site/src/components/login/auth-form.tsx | 11 ++- beszel/site/src/components/routes/system.tsx | 2 +- 5 files changed, 80 insertions(+), 33 deletions(-) diff --git a/beszel/internal/agent/agent.go b/beszel/internal/agent/agent.go index 332f8c9a..3a957134 100644 --- a/beszel/internal/agent/agent.go +++ b/beszel/internal/agent/agent.go @@ -70,13 +70,15 @@ func (a *Agent) Run(pubKey []byte, addr string) { // if debugging, print stats if a.debug { - slog.Debug("Stats", "data", a.gatherStats()) + slog.Debug("Stats", "data", a.GatherStats()) } - a.startServer(pubKey, addr) + if pubKey != nil { + a.startServer(pubKey, addr) + } } -func (a *Agent) gatherStats() system.CombinedData { +func (a *Agent) GatherStats() system.CombinedData { systemData := system.CombinedData{ Stats: a.getSystemStats(), Info: a.systemInfo, diff --git a/beszel/internal/agent/server.go b/beszel/internal/agent/server.go index fb773cd1..22a31a95 100644 --- a/beszel/internal/agent/server.go +++ b/beszel/internal/agent/server.go @@ -24,7 +24,7 @@ func (a *Agent) startServer(pubKey []byte, addr string) { } func (a *Agent) handleSession(s sshServer.Session) { - stats := a.gatherStats() + stats := a.GatherStats() slog.Debug("Sending stats", "data", stats) if err := json.NewEncoder(s).Encode(stats); err != nil { slog.Error("Error encoding stats", "err", err) diff --git a/beszel/internal/hub/hub.go b/beszel/internal/hub/hub.go index 19ca0161..b509bbed 100644 --- a/beszel/internal/hub/hub.go +++ b/beszel/internal/hub/hub.go @@ -3,6 +3,7 @@ package hub import ( "beszel" + "beszel/internal/agent" "beszel/internal/alerts" "beszel/internal/entities/system" "beszel/internal/records" @@ -42,6 +43,7 @@ type Hub struct { am *alerts.AlertManager um *users.UserManager rm *records.RecordManager + hubAgent *agent.Agent } func NewHub(app *pocketbase.PocketBase) *Hub { @@ -56,10 +58,6 @@ func NewHub(app *pocketbase.PocketBase) *Hub { } func (h *Hub) Run() { - // rm := records.NewRecordManager(h.app) - // am := alerts.NewAlertManager(h.app) - // um := users.NewUserManager(h.app) - // loosely check if it was executed using "go run" isGoRun := strings.HasPrefix(os.Args[0], os.TempDir()) @@ -73,25 +71,22 @@ func (h *Hub) Run() { // initial setup h.app.OnBeforeServe().Add(func(e *core.ServeEvent) error { // create ssh client config - err := h.createSSHClientConfig() - if err != nil { + if err := h.createSSHClientConfig(); err != nil { log.Fatal(err) } // set auth settings - usersCollection, err := h.app.Dao().FindCollectionByNameOrId("users") - if err != nil { - return err - } - usersAuthOptions := usersCollection.AuthOptions() - usersAuthOptions.AllowUsernameAuth = false - if os.Getenv("DISABLE_PASSWORD_AUTH") == "true" { - usersAuthOptions.AllowEmailAuth = false - } else { - usersAuthOptions.AllowEmailAuth = true - } - usersCollection.SetOptions(usersAuthOptions) - if err := h.app.Dao().SaveCollection(usersCollection); err != nil { - return err + if usersCollection, err := h.app.Dao().FindCollectionByNameOrId("users"); err == nil { + usersAuthOptions := usersCollection.AuthOptions() + usersAuthOptions.AllowUsernameAuth = false + if os.Getenv("DISABLE_PASSWORD_AUTH") == "true" { + usersAuthOptions.AllowEmailAuth = false + } else { + usersAuthOptions.AllowEmailAuth = true + } + usersCollection.SetOptions(usersAuthOptions) + if err := h.app.Dao().SaveCollection(usersCollection); err != nil { + return err + } } return nil }) @@ -159,6 +154,16 @@ func (h *Hub) Run() { // system creation defaults h.app.OnModelBeforeCreate("systems").Add(func(e *core.ModelEvent) error { record := e.Model.(*models.Record) + if record.GetString("host") == "hubsys" { + // todo: check for hubsys existance and return error if exists (or make sure user is admin) + if record.GetString("name") == "x" { + hostname, _ := os.Hostname() + if hostname == "" { + hostname = "localhost" + } + record.Set("name", hostname) + } + } record.Set("info", system.Info{}) record.Set("status", "pending") return nil @@ -246,6 +251,26 @@ func (h *Hub) updateSystems() { } func (h *Hub) updateSystem(record *models.Record) { + switch record.GetString("host") { + case "hubsys": + h.updateHubSystem(record) + default: + h.updateRemoteSystem(record) + } +} + +// Update hub system stats with built-in agent +func (h *Hub) updateHubSystem(record *models.Record) { + if h.hubAgent == nil { + h.hubAgent = agent.NewAgent() + h.hubAgent.Run(nil, "") + } + systemData := h.hubAgent.GatherStats() + h.saveSystemStats(record, &systemData) +} + +// Connect to remote system and update system stats +func (h *Hub) updateRemoteSystem(record *models.Record) { var client *ssh.Client var err error @@ -273,7 +298,7 @@ func (h *Hub) updateSystem(record *models.Record) { // if previous connection was closed, try again h.app.Logger().Error("Existing SSH connection closed. Retrying...", "host", record.GetString("host"), "port", record.GetString("port")) h.deleteSystemConnection(record) - h.updateSystem(record) + h.updateRemoteSystem(record) return } h.app.Logger().Error("Failed to get system stats: ", "err", err.Error()) @@ -281,6 +306,11 @@ func (h *Hub) updateSystem(record *models.Record) { return } // update system record + h.saveSystemStats(record, &systemData) +} + +// Update system record with provided system.CombinedData +func (h *Hub) saveSystemStats(record *models.Record, systemData *system.CombinedData) { record.Set("status", "up") record.Set("info", systemData.Info) if err := h.app.Dao().SaveRecord(record); err != nil { @@ -320,14 +350,20 @@ func (h *Hub) updateSystemStatus(record *models.Record, status string) { } } +// Deletes the SSH connection (remote) or built-in agent reference func (h *Hub) deleteSystemConnection(record *models.Record) { - if _, ok := h.systemConnections[record.Id]; ok { - if h.systemConnections[record.Id] != nil { - h.systemConnections[record.Id].Close() + switch record.GetString("host") { + case "hubsys": + h.hubAgent = nil + default: + if _, ok := h.systemConnections[record.Id]; ok { + if h.systemConnections[record.Id] != nil { + h.systemConnections[record.Id].Close() + } + h.connectionLock.Lock() + defer h.connectionLock.Unlock() + delete(h.systemConnections, record.Id) } - h.connectionLock.Lock() - defer h.connectionLock.Unlock() - delete(h.systemConnections, record.Id) } } diff --git a/beszel/site/src/components/login/auth-form.tsx b/beszel/site/src/components/login/auth-form.tsx index ade4bdb1..26ad32eb 100644 --- a/beszel/site/src/components/login/auth-form.tsx +++ b/beszel/site/src/components/login/auth-form.tsx @@ -94,13 +94,15 @@ export function UserAuthForm({ setErrors({ passwordConfirm: msg }) return } + // create admin user await pb.admins.create({ email, password, passwordConfirm: password, }) await pb.admins.authWithPassword(email, password) - await pb.collection('users').create({ + // create regular user + const user = await pb.collection('users').create({ username, email, password, @@ -108,6 +110,13 @@ export function UserAuthForm({ role: 'admin', verified: true, }) + // create hubsys + await pb.collection('systems').create({ + name: 'x', + port: 'x', + host: 'hubsys', + users: user.id, + }) await pb.collection('users').authWithPassword(email, password) } else { await pb.collection('users').authWithPassword(email, password) diff --git a/beszel/site/src/components/routes/system.tsx b/beszel/site/src/components/routes/system.tsx index 86f314bb..4995201d 100644 --- a/beszel/site/src/components/routes/system.tsx +++ b/beszel/site/src/components/routes/system.tsx @@ -197,7 +197,7 @@ export default function SystemDetail({ name }: { name: string }) { uptime = `${Math.trunc(system.info?.u / 86400)} days` } return [ - { value: system.host, Icon: GlobeIcon }, + { value: system.host, Icon: GlobeIcon, hide: system.host === 'hubsys' }, { value: system.info.h, Icon: MonitorIcon,