mirror of
https://github.com/fankes/beszel.git
synced 2025-10-19 01:39:34 +08:00
updates
This commit is contained in:
53
main.go
53
main.go
@@ -160,7 +160,7 @@ func main() {
|
||||
|
||||
// immediately create connection for new servers
|
||||
app.OnModelAfterCreate("systems").Add(func(e *core.ModelEvent) error {
|
||||
go updateServer(e.Model.(*models.Record))
|
||||
go updateSystem(e.Model.(*models.Record))
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -175,10 +175,11 @@ func main() {
|
||||
deleteServerConnection(newRecord)
|
||||
}
|
||||
|
||||
// if server is set to pending (unpause), try to connect
|
||||
if newStatus == "pending" {
|
||||
go updateServer(newRecord)
|
||||
}
|
||||
// if server is set to pending (unpause), try to connect immediately
|
||||
// commenting out because we don't want to get off of the one min schedule
|
||||
// if newStatus == "pending" {
|
||||
// go updateSystem(newRecord)
|
||||
// }
|
||||
|
||||
// alerts
|
||||
handleStatusAlerts(newStatus, oldRecord)
|
||||
@@ -198,33 +199,35 @@ func main() {
|
||||
}
|
||||
|
||||
func serverUpdateTicker() {
|
||||
ticker := time.NewTicker(60 * time.Second)
|
||||
ticker := time.NewTicker(15 * time.Second)
|
||||
for range ticker.C {
|
||||
updateServers()
|
||||
updateSystems()
|
||||
}
|
||||
}
|
||||
|
||||
func updateServers() {
|
||||
// serverCount := len(serverConnections)
|
||||
// fmt.Println("server count: ", serverCount)
|
||||
query := app.Dao().RecordQuery("systems").
|
||||
Where(dbx.NewExp("status != \"paused\"")).
|
||||
OrderBy("updated ASC").
|
||||
// todo get total count of servers and divide by 4 or something
|
||||
Limit(5)
|
||||
|
||||
records := []*models.Record{}
|
||||
if err := query.All(&records); err != nil {
|
||||
app.Logger().Error("Failed to get servers: ", "err", err.Error())
|
||||
// return nil, err
|
||||
func updateSystems() {
|
||||
// handle max of 1/3 + 1 servers at a time
|
||||
numServers := len(serverConnections)/3 + 1
|
||||
// find systems that are not paused and updated more than 58 seconds ago
|
||||
fiftyEightSecondsAgo := time.Now().UTC().Add(-58 * time.Second).Format("2006-01-02 15:04:05")
|
||||
records, err := app.Dao().FindRecordsByFilter(
|
||||
"2hz5ncl8tizk5nx", // collection
|
||||
"status != 'paused' && updated < {:updated}", // filter
|
||||
"updated", // sort
|
||||
numServers, // limit
|
||||
0, // offset
|
||||
dbx.Params{"updated": fiftyEightSecondsAgo},
|
||||
)
|
||||
if err != nil {
|
||||
app.Logger().Error("Failed to query systems: ", "err", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
updateServer(record)
|
||||
updateSystem(record)
|
||||
}
|
||||
}
|
||||
|
||||
func updateServer(record *models.Record) {
|
||||
func updateSystem(record *models.Record) {
|
||||
var server Server
|
||||
// check if server connection data exists
|
||||
if _, ok := serverConnections[record.Id]; ok {
|
||||
@@ -251,7 +254,7 @@ func updateServer(record *models.Record) {
|
||||
// if previous connection was closed, try again
|
||||
app.Logger().Error("Existing SSH connection closed. Retrying...", "host", server.Host, "port", server.Port)
|
||||
deleteServerConnection(record)
|
||||
updateServer(record)
|
||||
updateSystem(record)
|
||||
return
|
||||
}
|
||||
app.Logger().Error("Failed to get server stats: ", "err", err.Error())
|
||||
@@ -269,6 +272,7 @@ func updateServer(record *models.Record) {
|
||||
system_stats_record := models.NewRecord(system_stats)
|
||||
system_stats_record.Set("system", record.Id)
|
||||
system_stats_record.Set("stats", systemData.Stats)
|
||||
system_stats_record.Set("type", "1m")
|
||||
if err := app.Dao().SaveRecord(system_stats_record); err != nil {
|
||||
app.Logger().Error("Failed to save record: ", "err", err.Error())
|
||||
}
|
||||
@@ -278,6 +282,7 @@ func updateServer(record *models.Record) {
|
||||
container_stats_record := models.NewRecord(container_stats)
|
||||
container_stats_record.Set("system", record.Id)
|
||||
container_stats_record.Set("stats", systemData.Containers)
|
||||
container_stats_record.Set("type", "1m")
|
||||
if err := app.Dao().SaveRecord(container_stats_record); err != nil {
|
||||
app.Logger().Error("Failed to save record: ", "err", err.Error())
|
||||
}
|
||||
|
@@ -272,7 +272,7 @@ func init() {
|
||||
"minPasswordLength": 8,
|
||||
"onlyEmailDomains": null,
|
||||
"onlyVerified": true,
|
||||
"requireEmail": true
|
||||
"requireEmail": false
|
||||
}
|
||||
},
|
||||
{
|
||||
|
55
readme.md
55
readme.md
@@ -11,6 +11,19 @@ A lightweight resource monitoring hub with historical data, docker stats, and al
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Features
|
||||
|
||||
- **Historical data**: Stats are available for up to 30 days.
|
||||
- **Docker stats**: View CPU and memory usage for each container.
|
||||
- **Alerts**: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
- **Lightweight**: Much smaller and less demanding than leading solutions.
|
||||
- **Secure**: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
- **Simple setup**: Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
- **Multi-user**: Each user has their own systems. Admins can share systems across users.
|
||||
- **Oauth / OIDC**: Supports many OAuth2 providers and password auth can be disabled.
|
||||
- **Automated backups**: Automatically back up your data to S3-compatible storage.
|
||||
- **Open source**: MIT license and no paywalled features.
|
||||
|
||||
## Introduction
|
||||
|
||||
Beszel has two components: the hub and the agent.
|
||||
@@ -23,19 +36,20 @@ The agent runs on each system you want to monitor. It provides a minimal SSH ser
|
||||
|
||||
The hub and agent are distributed as single binary files, as well as docker images.
|
||||
|
||||
> **Note**: The docker version does not support disk I/O stats, so use the binary version if that's important to you.
|
||||
|
||||
### Docker
|
||||
|
||||
> **Note**: The docker version cannot automatically detect the filesystem to use for disk I/O stats, so use the binary version if that's important to you.
|
||||
|
||||
### Binary
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Name | Default | Description |
|
||||
| ----------------------- | ------- | ------------------------------------------------- |
|
||||
| `DISABLE_PASSWORD_AUTH` | unset | Disables password authentication if set to `true` |
|
||||
| Name | Default | Description |
|
||||
| ----------------------- | ------- | ------------------------------------------------ |
|
||||
| `DISABLE_PASSWORD_AUTH` | false | Disables password authentication |
|
||||
| `FILESYSTEM` | unset | Filesystem / partition to use for disk I/O stats |
|
||||
|
||||
## OAuth / OIDC integration
|
||||
## OAuth / OIDC setup
|
||||
|
||||
Beszel supports OpenID Connect and many OAuth2 authentication providers (see list below). To enable this, you will need to:
|
||||
|
||||
@@ -61,7 +75,6 @@ Beszel supports OpenID Connect and many OAuth2 authentication providers (see lis
|
||||
- Microsoft
|
||||
- OpenID Connect
|
||||
- Patreon (v2)
|
||||
- Planning Center
|
||||
- Spotify
|
||||
- Strava
|
||||
- Twitch
|
||||
@@ -80,4 +93,30 @@ The hub and agent communicate over SSH, so they do not need to be exposed to the
|
||||
|
||||
When the hub is started for the first time, it generates an ED25519 key pair.
|
||||
|
||||
The agent's SSH server is configured to only accept connections using this key. It also does not provide a pty or accept any input, so it is not possible to execute commands on the agent.
|
||||
The agent's SSH server is configured to accept connections only using this key. It also does not provide a pty or accept any input, so it is not possible to execute commands on the agent.
|
||||
|
||||
## FAQ / Troubleshooting
|
||||
|
||||
### Agent is not connecting
|
||||
|
||||
Assuming the agent is running, the connection is probably being blocked by a firewall. You should add an inbound rule to allow TCP connections to the port. Check any active firewalls on the agent system, like iptables or ufw, and in your cloud provider account if applicable.
|
||||
|
||||
Connectivity can be tested by running `telnet <agent-ip> <port>` or `nc -zv <agent-ip> <port>` from a remote machine.
|
||||
|
||||
### Finding the correct filesystem
|
||||
|
||||
The filesystem / partition to use for disk I/O stats is specified in the `FILESYSTEM` environment variable.
|
||||
|
||||
If it's not set, the agent will try to find the filesystem mounted on `/` and use that. This doesn't seem to work in a container, so it's recommended to set this value. One of the following methods should work (you usually want the option mounted on `/`):
|
||||
|
||||
- Run `df -h` and choose an option under "Filesystem"
|
||||
- Run `lsblk` and choose an option under "NAME"
|
||||
- Run `sudo fdisk -l` and choose an option under "Device"
|
||||
|
||||
> Note: the first reading always comes in as 0 bytes because it needs to establish baseline values.
|
||||
|
||||
### Month / week records are not populating reliably
|
||||
|
||||
Records for longer time periods are made by averaging stats from the shorter time periods. They require the agent to be running uninterrupted for long enough to get a full set of data.
|
||||
|
||||
If you pause / unpause the agent for longer than one minute, the data will be incomplete and the timing for the current interval will reset.
|
||||
|
BIN
site/bun.lockb
BIN
site/bun.lockb
Binary file not shown.
@@ -10,6 +10,7 @@ import { chartTimeData, cn, getPbTimestamp } from '@/lib/utils'
|
||||
import { Separator } from '../ui/separator'
|
||||
import { scaleTime } from 'd3-scale'
|
||||
import DiskIoChart from '../charts/disk-io-chart'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip'
|
||||
|
||||
const CpuChart = lazy(() => import('../charts/cpu-chart'))
|
||||
const ContainerCpuChart = lazy(() => import('../charts/container-cpu-chart'))
|
||||
@@ -218,15 +219,28 @@ export default function ServerDetail({ name }: { name: string }) {
|
||||
<div className="flex gap-1.5 items-center">
|
||||
<GlobeIcon className="h-4 w-4" /> {server.host}
|
||||
</div>
|
||||
<Separator orientation="vertical" className="h-4 bg-primary/30" />
|
||||
<div className="flex gap-1.5 items-center">
|
||||
<ClockArrowUp className="h-4 w-4" /> {uptime}
|
||||
</div>
|
||||
<Separator orientation="vertical" className="h-4 bg-primary/30" />
|
||||
<div className="flex gap-1.5 items-center">
|
||||
<CpuIcon className="h-4 w-4" />
|
||||
{server.info?.m} ({server.info?.c}c / {server.info.t}t)
|
||||
</div>
|
||||
{server.info?.u && (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<Separator orientation="vertical" className="h-4 bg-primary/30" />
|
||||
<TooltipTrigger asChild>
|
||||
<div className="flex gap-1.5 items-center">
|
||||
<ClockArrowUp className="h-4 w-4" /> {uptime}
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Uptime</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
{server.info?.m && (
|
||||
<>
|
||||
<Separator orientation="vertical" className="h-4 bg-primary/30" />
|
||||
<div className="flex gap-1.5 items-center">
|
||||
<CpuIcon className="h-4 w-4" />
|
||||
{server.info.m} ({server.info.c}c / {server.info.t}t)
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
Reference in New Issue
Block a user