mirror of
https://github.com/henrygd/beszel.git
synced 2026-09-22 01:17:48 +02:00
Compare commits
1 Commits
main
...
88a9ad283c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88a9ad283c |
@@ -68,11 +68,10 @@ type dockerManager struct {
|
|||||||
excludeContainers []string // Patterns to exclude containers by name
|
excludeContainers []string // Patterns to exclude containers by name
|
||||||
usingPodman bool // Whether the Docker Engine API is running on Podman
|
usingPodman bool // Whether the Docker Engine API is running on Podman
|
||||||
|
|
||||||
registryClient *http.Client // Client for registry requests; nil uses a client with a 10-second timeout
|
registryClient *http.Client // Client for registry requests; nil uses a client with a 10-second timeout
|
||||||
imageUpdatesDisabled bool // Whether image update checks are disabled by configuration
|
imageUpdatesMutex sync.RWMutex // Protects imageUpdates, its entries, and imageUpdatesRunning
|
||||||
imageUpdatesMutex sync.RWMutex // Protects imageUpdates, its entries, and imageUpdatesRunning
|
imageUpdates map[string]*imageUpdateStatus // Shared update status keyed by normalized image reference
|
||||||
imageUpdates map[string]*imageUpdateStatus // Shared update status keyed by normalized image reference
|
imageUpdatesRunning bool // Whether a background image-update batch is in progress
|
||||||
imageUpdatesRunning bool // Whether a background image-update batch is in progress
|
|
||||||
|
|
||||||
// Cache-time-aware tracking for CPU stats (similar to cpu.go)
|
// Cache-time-aware tracking for CPU stats (similar to cpu.go)
|
||||||
// Maps cache time intervals to container-specific CPU usage tracking
|
// Maps cache time intervals to container-specific CPU usage tracking
|
||||||
@@ -689,8 +688,6 @@ func newDockerManager(agent *Agent) *dockerManager {
|
|||||||
userAgent: "Docker-Client/",
|
userAgent: "Docker-Client/",
|
||||||
}
|
}
|
||||||
|
|
||||||
dockerImageCheck, _ := utils.GetEnv("DOCKER_IMAGE_CHECK")
|
|
||||||
|
|
||||||
// Read container exclusion patterns from environment variable
|
// Read container exclusion patterns from environment variable
|
||||||
var excludeContainers []string
|
var excludeContainers []string
|
||||||
if excludeStr, set := utils.GetEnv("EXCLUDE_CONTAINERS"); set && excludeStr != "" {
|
if excludeStr, set := utils.GetEnv("EXCLUDE_CONTAINERS"); set && excludeStr != "" {
|
||||||
@@ -710,11 +707,10 @@ func newDockerManager(agent *Agent) *dockerManager {
|
|||||||
Timeout: timeout,
|
Timeout: timeout,
|
||||||
Transport: userAgentTransport,
|
Transport: userAgentTransport,
|
||||||
},
|
},
|
||||||
containerStatsMap: make(map[string]*container.Stats),
|
containerStatsMap: make(map[string]*container.Stats),
|
||||||
sem: make(chan struct{}, 5),
|
sem: make(chan struct{}, 5),
|
||||||
apiContainerList: []*container.ApiInfo{},
|
apiContainerList: []*container.ApiInfo{},
|
||||||
excludeContainers: excludeContainers,
|
excludeContainers: excludeContainers,
|
||||||
imageUpdatesDisabled: dockerImageCheck == "false",
|
|
||||||
|
|
||||||
// Initialize cache-time-aware tracking structures
|
// Initialize cache-time-aware tracking structures
|
||||||
lastCpuContainer: make(map[uint16]map[string]uint64),
|
lastCpuContainer: make(map[uint16]map[string]uint64),
|
||||||
|
|||||||
@@ -31,9 +31,6 @@ func normalizedImageReference(image string) string {
|
|||||||
// refreshImageUpdates starts at most one background batch. Neither its network
|
// refreshImageUpdates starts at most one background batch. Neither its network
|
||||||
// work nor its completion is part of the container metrics wait group.
|
// work nor its completion is part of the container metrics wait group.
|
||||||
func (dm *dockerManager) refreshImageUpdates(containers []*container.ApiInfo, now time.Time) {
|
func (dm *dockerManager) refreshImageUpdates(containers []*container.ApiInfo, now time.Time) {
|
||||||
if dm.imageUpdatesDisabled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dm.imageUpdatesMutex.Lock()
|
dm.imageUpdatesMutex.Lock()
|
||||||
defer dm.imageUpdatesMutex.Unlock()
|
defer dm.imageUpdatesMutex.Unlock()
|
||||||
if dm.imageUpdatesRunning {
|
if dm.imageUpdatesRunning {
|
||||||
|
|||||||
@@ -27,29 +27,6 @@ func waitForImageUpdates(t *testing.T, dm *dockerManager) {
|
|||||||
}, time.Second*3, time.Millisecond)
|
}, time.Second*3, time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDisableDockerImageUpdateCheck(t *testing.T) {
|
|
||||||
t.Setenv("BESZEL_AGENT_DOCKER_IMAGE_CHECK", "false")
|
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.URL.Path == "/version" {
|
|
||||||
fmt.Fprint(w, `{"Version":"25.0.0"}`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
http.NotFound(w, r)
|
|
||||||
}))
|
|
||||||
defer server.Close()
|
|
||||||
t.Setenv("BESZEL_AGENT_DOCKER_HOST", server.URL)
|
|
||||||
|
|
||||||
dm := newDockerManager(nil)
|
|
||||||
require.True(t, dm.imageUpdatesDisabled)
|
|
||||||
dm.registryClient = &http.Client{Transport: roundTripFunc(func(*http.Request) (*http.Response, error) {
|
|
||||||
t.Fatal("disabled image update check made a registry request")
|
|
||||||
return nil, nil
|
|
||||||
})}
|
|
||||||
dm.refreshImageUpdates([]*container.ApiInfo{{Image: "nginx", Names: []string{"/nginx"}}}, time.Now())
|
|
||||||
require.False(t, dm.imageUpdatesRunning)
|
|
||||||
require.Nil(t, dm.imageUpdates)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestImageUpdateCacheAndStats(t *testing.T) {
|
func TestImageUpdateCacheAndStats(t *testing.T) {
|
||||||
local := "sha256:" + strings.Repeat("a", 64)
|
local := "sha256:" + strings.Repeat("a", 64)
|
||||||
remote := "sha256:" + strings.Repeat("b", 64)
|
remote := "sha256:" + strings.Repeat("b", 64)
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/henrygd/beszel"
|
"github.com/henrygd/beszel"
|
||||||
@@ -16,12 +14,6 @@ import (
|
|||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
type noKeyProvidedError struct{}
|
|
||||||
|
|
||||||
func (noKeyProvidedError) Error() string {
|
|
||||||
return "no key provided: must set -key flag, KEY env var, or KEY_FILE env var. Use 'beszel-agent help' for usage"
|
|
||||||
}
|
|
||||||
|
|
||||||
// cli options
|
// cli options
|
||||||
type cmdOptions struct {
|
type cmdOptions struct {
|
||||||
key string // key is the public key(s) for SSH authentication.
|
key string // key is the public key(s) for SSH authentication.
|
||||||
@@ -132,7 +124,7 @@ func (opts *cmdOptions) loadPublicKeys() ([]ssh.PublicKey, error) {
|
|||||||
// Try key file
|
// Try key file
|
||||||
keyFile, ok := utils.GetEnv("KEY_FILE")
|
keyFile, ok := utils.GetEnv("KEY_FILE")
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, noKeyProvidedError{}
|
return nil, fmt.Errorf("no key provided: must set -key flag, KEY env var, or KEY_FILE env var. Use 'beszel-agent help' for usage")
|
||||||
}
|
}
|
||||||
|
|
||||||
pubKey, err := os.ReadFile(keyFile)
|
pubKey, err := os.ReadFile(keyFile)
|
||||||
@@ -146,14 +138,6 @@ func (opts *cmdOptions) getAddress() string {
|
|||||||
return agent.GetAddress(opts.listen)
|
return agent.GetAddress(opts.listen)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isBenignStartupError(err error, goos string) bool {
|
|
||||||
if goos != "windows" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
var noKeyErr noKeyProvidedError
|
|
||||||
return errors.As(err, &noKeyErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleFingerprint handles the "fingerprint" command with subcommands "view" and "reset".
|
// handleFingerprint handles the "fingerprint" command with subcommands "view" and "reset".
|
||||||
func handleFingerprint() {
|
func handleFingerprint() {
|
||||||
subCmd := ""
|
subCmd := ""
|
||||||
@@ -198,12 +182,6 @@ func main() {
|
|||||||
var err error
|
var err error
|
||||||
serverConfig.Keys, err = opts.loadPublicKeys()
|
serverConfig.Keys, err = opts.loadPublicKeys()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isBenignStartupError(err, runtime.GOOS) {
|
|
||||||
// WinGet launches the executable without configuration during validation.
|
|
||||||
// Exit successfully in that case while retaining the error on other platforms.
|
|
||||||
log.Print("Failed to load public keys:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Fatal("Failed to load public keys:", err)
|
log.Fatal("Failed to load public keys:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"errors"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -188,26 +187,6 @@ func TestLoadPublicKeys(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsBenignStartupError(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
err error
|
|
||||||
goos string
|
|
||||||
want bool
|
|
||||||
}{
|
|
||||||
{name: "missing key on windows", err: noKeyProvidedError{}, goos: "windows", want: true},
|
|
||||||
{name: "wrapped missing key on windows", err: errors.Join(errors.New("startup failed"), noKeyProvidedError{}), goos: "windows", want: true},
|
|
||||||
{name: "missing key on linux", err: noKeyProvidedError{}, goos: "linux", want: false},
|
|
||||||
{name: "different error on windows", err: errors.New("invalid key"), goos: "windows", want: false},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
assert.Equal(t, tt.want, isBenignStartupError(tt.err, tt.goos))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetNetwork(t *testing.T) {
|
func TestGetNetwork(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
@@ -70,9 +70,7 @@ RUN set -eux; \
|
|||||||
# --------------------------
|
# --------------------------
|
||||||
FROM --platform=$TARGETPLATFORM debian:bookworm-slim AS zfsutils-builder
|
FROM --platform=$TARGETPLATFORM debian:bookworm-slim AS zfsutils-builder
|
||||||
|
|
||||||
# zfsutils-linux is distributed in Debian's contrib component.
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
RUN sed -i 's/Components: main/Components: main contrib/' /etc/apt/sources.list.d/debian.sources \
|
|
||||||
&& apt-get update && apt-get install -y --no-install-recommends \
|
|
||||||
zfsutils-linux \
|
zfsutils-linux \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
|||||||
@@ -211,6 +211,7 @@ export default memo(function SystemDetail({ id }: { id: string }) {
|
|||||||
<TemperatureChart {...coreProps} setPageBottomExtraMargin={setPageBottomExtraMargin} />
|
<TemperatureChart {...coreProps} setPageBottomExtraMargin={setPageBottomExtraMargin} />
|
||||||
<FanChart {...coreProps} />
|
<FanChart {...coreProps} />
|
||||||
<BatteryChart system={system} {...coreProps} />
|
<BatteryChart system={system} {...coreProps} />
|
||||||
|
<SwapChart chartData={chartData} grid={grid} dataEmpty={dataEmpty} systemStats={systemStats} />
|
||||||
{pageBottomExtraMargin > 0 && <div style={{ marginBottom: pageBottomExtraMargin }}></div>}
|
{pageBottomExtraMargin > 0 && <div style={{ marginBottom: pageBottomExtraMargin }}></div>}
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ type MonitorChartBaseProps = MonitorChartProps & {
|
|||||||
tickFormatter: (value: number) => string
|
tickFormatter: (value: number) => string
|
||||||
contentFormatter: ({ value }: { value: number | string }) => string | number
|
contentFormatter: ({ value }: { value: number | string }) => string | number
|
||||||
domain?: [number | "auto", number | "auto"]
|
domain?: [number | "auto", number | "auto"]
|
||||||
/** Overrides the per-monitor line colors (e.g. a fixed color for single-monitor charts). */
|
|
||||||
color?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function MonitorChart({
|
function MonitorChart({
|
||||||
@@ -43,7 +41,6 @@ function MonitorChart({
|
|||||||
tickFormatter,
|
tickFormatter,
|
||||||
contentFormatter,
|
contentFormatter,
|
||||||
domain,
|
domain,
|
||||||
color,
|
|
||||||
showFilter = monitors.length > 1,
|
showFilter = monitors.length > 1,
|
||||||
}: MonitorChartBaseProps) {
|
}: MonitorChartBaseProps) {
|
||||||
const storedFilter = useStore($monitorFilter)
|
const storedFilter = useStore($monitorFilter)
|
||||||
@@ -70,12 +67,11 @@ function MonitorChart({
|
|||||||
label,
|
label,
|
||||||
dataKey: (record: NetworkMonitorStatsRecord) => record.stats?.[p.id]?.[metric] ?? null,
|
dataKey: (record: NetworkMonitorStatsRecord) => record.stats?.[p.id]?.[metric] ?? null,
|
||||||
dot,
|
dot,
|
||||||
color:
|
color: count <= 5 ? i + 1 : `hsl(${(i * 360) / count}, var(--chart-saturation), var(--chart-lightness))`,
|
||||||
color ?? (count <= 5 ? i + 1 : `hsl(${(i * 360) / count}, var(--chart-saturation), var(--chart-lightness))`),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return { dataPoints: points, visibleKeys: visibleIDs }
|
return { dataPoints: points, visibleKeys: visibleIDs }
|
||||||
}, [monitors, filter, metric, chartData.chartTime, color])
|
}, [monitors, filter, metric, chartData.chartTime])
|
||||||
|
|
||||||
const filteredMonitorStats = useMemo(() => {
|
const filteredMonitorStats = useMemo(() => {
|
||||||
if (!visibleKeys.length) return monitorStats
|
if (!visibleKeys.length) return monitorStats
|
||||||
@@ -204,7 +200,6 @@ export function LossChart({ monitorStats, grid, monitors, chartData, empty, titl
|
|||||||
title={title}
|
title={title}
|
||||||
description={t`Packet loss (%)`}
|
description={t`Packet loss (%)`}
|
||||||
domain={[0, 100]}
|
domain={[0, 100]}
|
||||||
color="var(--destructive)"
|
|
||||||
tickFormatter={(value) => `${toFixedFloat(value, value >= 10 ? 0 : 1)}%`}
|
tickFormatter={(value) => `${toFixedFloat(value, value >= 10 ? 0 : 1)}%`}
|
||||||
contentFormatter={({ value }) => {
|
contentFormatter={({ value }) => {
|
||||||
if (typeof value !== "number") {
|
if (typeof value !== "number") {
|
||||||
|
|||||||
@@ -15,10 +15,9 @@ It has a friendly web interface, simple configuration, and is ready to use out o
|
|||||||
|
|
||||||
- **Lightweight**: Smaller and less resource-intensive than leading solutions.
|
- **Lightweight**: Smaller and less resource-intensive than leading solutions.
|
||||||
- **Simple**: Easy setup with little manual configuration required.
|
- **Simple**: Easy setup with little manual configuration required.
|
||||||
- **Alerts**: Configurable alerts for most metrics. Supports many notification services.
|
|
||||||
- **Docker stats**: Tracks CPU, memory, and network usage history for each container.
|
- **Docker stats**: Tracks CPU, memory, and network usage history for each container.
|
||||||
- **Network monitoring**: Monitor response time and interruptions directly from agents.
|
- **ZFS**: Tracks pool capacity, health, and I/O, plus per-dataset usage.
|
||||||
- **S.M.A.R.T.**: Disk health data and notifications on drive failure.
|
- **Alerts**: Configurable alerts for CPU, memory, disk, bandwidth, temperature, fan speed, load average, and status.
|
||||||
- **Multi-user**: Users manage their own systems. Admins can share systems across users.
|
- **Multi-user**: Users manage their own systems. Admins can share systems across users.
|
||||||
- **OAuth / OIDC**: Supports many OAuth2 providers. Password auth can be disabled.
|
- **OAuth / OIDC**: Supports many OAuth2 providers. Password auth can be disabled.
|
||||||
- **Automatic backups**: Save to and restore from disk or S3-compatible storage.
|
- **Automatic backups**: Save to and restore from disk or S3-compatible storage.
|
||||||
@@ -52,7 +51,7 @@ The [quick start guide](https://beszel.dev/guide/getting-started) and other docu
|
|||||||
- **Temperature** - Host system sensors.
|
- **Temperature** - Host system sensors.
|
||||||
- **Fan speed** - Host system sensors (Linux, via `/sys/class/hwmon`).
|
- **Fan speed** - Host system sensors (Linux, via `/sys/class/hwmon`).
|
||||||
- **GPU usage / power draw** - Nvidia, AMD, and Intel.
|
- **GPU usage / power draw** - Nvidia, AMD, and Intel.
|
||||||
- **Battery charge** - Host system and some peripherals.
|
- **Battery** - Host system battery charge.
|
||||||
- **Containers** - Status and metrics of all running Docker / Podman containers.
|
- **Containers** - Status and metrics of all running Docker / Podman containers.
|
||||||
- **S.M.A.R.T.** - Host system disk health (includes eMMC wear/EOL and Linux mdraid array health via sysfs when available).
|
- **S.M.A.R.T.** - Host system disk health (includes eMMC wear/EOL and Linux mdraid array health via sysfs when available).
|
||||||
- **ZFS** - Pool capacity, usage, health, I/O throughput, scrub status, and per-dataset usage.
|
- **ZFS** - Pool capacity, usage, health, I/O throughput, scrub status, and per-dataset usage.
|
||||||
|
|||||||
@@ -298,94 +298,6 @@ warn() {
|
|||||||
echo "Warning: $*" >&2
|
echo "Warning: $*" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
# Keep IDs within 16 bits, including on older OpenWrt versions whose account
|
|
||||||
# helpers start automatic allocation at 65536.
|
|
||||||
openwrt_unused_id() {
|
|
||||||
awk -F: '
|
|
||||||
{ used[$3] = 1 }
|
|
||||||
END {
|
|
||||||
for (id = 32768; id < 65534; id++) {
|
|
||||||
if (!(id in used)) { print id; exit }
|
|
||||||
}
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
' "$1"
|
|
||||||
}
|
|
||||||
|
|
||||||
validate_openwrt_account() {
|
|
||||||
# Duplicate numeric IDs share permissions even when the names differ.
|
|
||||||
for account_file in /etc/passwd /etc/group; do
|
|
||||||
if ! awk -F: '
|
|
||||||
$1 == "beszel" { id = $3; count++ }
|
|
||||||
{ ids[$3]++ }
|
|
||||||
END { if (count && (count != 1 || id == 0 || ids[id] != 1)) exit 1 }
|
|
||||||
' "$account_file"; then
|
|
||||||
fail "The beszel account has a duplicate or root ID in $account_file. Stop beszel-agent and assign beszel an unused UID/GID before reinstalling. Update ownership only in $AGENT_DIR; do not change all files owned by the shared ID."
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
if grep -q '^beszel:' /etc/passwd; then
|
|
||||||
account_gid=$(awk -F: '$1 == "beszel" { print $4 }' /etc/passwd)
|
|
||||||
group_gid=$(awk -F: '$1 == "beszel" { print $3 }' /etc/group)
|
|
||||||
[ "$account_gid" = "$group_gid" ] || fail "The beszel user's primary group is not the dedicated beszel group. Repair the account before reinstalling."
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
configure_openwrt_account() (
|
|
||||||
validate_openwrt_account
|
|
||||||
[ -r /lib/functions.sh ] || fail "OpenWrt account helpers (/lib/functions.sh) are required."
|
|
||||||
# OpenWrt's library expects unset variables and defines generic functions.
|
|
||||||
# Source it in a subshell so neither affects the rest of the installer.
|
|
||||||
set +u
|
|
||||||
. /lib/functions.sh
|
|
||||||
IPKG_INSTROOT=""
|
|
||||||
|
|
||||||
if ! grep -q '^beszel:' /etc/group; then
|
|
||||||
account_gid=$(openwrt_unused_id /etc/group) || fail "No unused service GID available."
|
|
||||||
group_add beszel "$account_gid" || fail "Could not create the beszel group."
|
|
||||||
fi
|
|
||||||
account_gid=$(awk -F: '$1 == "beszel" { print $3 }' /etc/group)
|
|
||||||
[ -n "$account_gid" ] || fail "The beszel group was not created."
|
|
||||||
if ! grep -q '^beszel:' /etc/passwd; then
|
|
||||||
account_uid=$(openwrt_unused_id /etc/passwd) || fail "No unused service UID available."
|
|
||||||
user_add beszel "$account_uid" "$account_gid" "Beszel agent" /nonexistent /bin/false || fail "Could not create the beszel user."
|
|
||||||
fi
|
|
||||||
grep -q '^beszel:' /etc/passwd || fail "The beszel account is incomplete."
|
|
||||||
validate_openwrt_account
|
|
||||||
# Previous installers omitted the shadow entry. Repair it with a locked
|
|
||||||
# password, without rewriting an existing account or changing its IDs.
|
|
||||||
if ! grep -q '^beszel:' /etc/shadow; then
|
|
||||||
lock /var/lock/passwd || fail "Could not lock the account database."
|
|
||||||
shadow_status=0
|
|
||||||
if ! grep -q '^beszel:' /etc/shadow; then
|
|
||||||
printf 'beszel:!:0:0:99999:7:::\n' >> /etc/shadow || shadow_status=$?
|
|
||||||
fi
|
|
||||||
lock -u /var/lock/passwd || fail "Could not unlock the account database."
|
|
||||||
[ "$shadow_status" -eq 0 ] || fail "Could not create the beszel shadow entry."
|
|
||||||
fi
|
|
||||||
|
|
||||||
if grep -q '^docker:' /etc/group; then
|
|
||||||
# Match complete member names and avoid a leading comma for empty groups.
|
|
||||||
if ! awk -F: '$1 == "docker" { n = split($4, members, ","); for (i = 1; i <= n; i++) if (members[i] == "beszel") found = 1 } END { exit !found }' /etc/group; then
|
|
||||||
echo "Adding beszel to docker group"
|
|
||||||
if grep -q '^docker:[^:]*:[^:]*:$' /etc/group; then
|
|
||||||
sed -i '/^docker:/s/$/beszel/' /etc/group
|
|
||||||
else
|
|
||||||
sed -i '/^docker:/s/$/,beszel/' /etc/group
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
)
|
|
||||||
|
|
||||||
remove_openwrt_account() {
|
|
||||||
if command -v userdel >/dev/null 2>&1; then
|
|
||||||
userdel beszel || fail "Could not remove the beszel user."
|
|
||||||
elif command -v deluser >/dev/null 2>&1; then
|
|
||||||
deluser beszel || fail "Could not remove the beszel user."
|
|
||||||
else
|
|
||||||
warn "Neither userdel nor deluser is available; the beszel account has been retained."
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
require_value() {
|
require_value() {
|
||||||
[ "$#" -ge 2 ] && [ -n "$2" ] || fail "Option $1 requires a value."
|
[ "$#" -ge 2 ] && [ -n "$2" ] || fail "Option $1 requires a value."
|
||||||
}
|
}
|
||||||
@@ -760,9 +672,7 @@ if [ "$UNINSTALL" = true ]; then
|
|||||||
echo "Removing the dedicated user for the agent service..."
|
echo "Removing the dedicated user for the agent service..."
|
||||||
killall beszel-agent 2>/dev/null || true # Usually already stopped by the service manager.
|
killall beszel-agent 2>/dev/null || true # Usually already stopped by the service manager.
|
||||||
if id -u beszel >/dev/null 2>&1; then
|
if id -u beszel >/dev/null 2>&1; then
|
||||||
if is_openwrt; then
|
if is_alpine || is_openwrt; then
|
||||||
remove_openwrt_account
|
|
||||||
elif is_alpine; then
|
|
||||||
deluser beszel || fail "Could not remove the beszel user."
|
deluser beszel || fail "Could not remove the beszel user."
|
||||||
elif is_freebsd; then
|
elif is_freebsd; then
|
||||||
pw user del beszel || fail "Could not remove the beszel user."
|
pw user del beszel || fail "Could not remove the beszel user."
|
||||||
@@ -862,7 +772,32 @@ if is_alpine; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
elif is_openwrt; then
|
elif is_openwrt; then
|
||||||
configure_openwrt_account
|
# Create beszel group first if it doesn't exist (check /etc/group directly)
|
||||||
|
if ! grep -q "^beszel:" /etc/group >/dev/null 2>&1; then
|
||||||
|
echo "beszel:x:999:" >> /etc/group
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create beszel user if it doesn't exist (double-check to prevent duplicates)
|
||||||
|
if ! id -u beszel >/dev/null 2>&1 && ! grep -q "^beszel:" /etc/passwd >/dev/null 2>&1; then
|
||||||
|
echo "beszel:x:999:999::/nonexistent:/bin/false" >> /etc/passwd
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add the user to the docker group if docker group exists and user is not already in it
|
||||||
|
if grep -q "^docker:" /etc/group >/dev/null 2>&1; then
|
||||||
|
echo "Adding beszel to docker group"
|
||||||
|
# Check if beszel is already in docker group
|
||||||
|
if ! grep "^docker:" /etc/group | grep -q "beszel"; then
|
||||||
|
# Add beszel to docker group by modifying /etc/group
|
||||||
|
# Handle both cases: group with existing members and group without members
|
||||||
|
if grep "^docker:" /etc/group | grep -q ":.*:.*$"; then
|
||||||
|
# Group has existing members, append with comma
|
||||||
|
sed -i 's/^docker:\([^:]*:[^:]*:\)\(.*\)$/docker:\1\2,beszel/' /etc/group
|
||||||
|
else
|
||||||
|
# Group has no members, just append
|
||||||
|
sed -i 's/^docker:\([^:]*:[^:]*:\)$/docker:\1beszel/' /etc/group
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
elif is_freebsd; then
|
elif is_freebsd; then
|
||||||
if is_opnsense || is_pfsense; then
|
if is_opnsense || is_pfsense; then
|
||||||
|
|||||||
Reference in New Issue
Block a user