mirror of
https://github.com/henrygd/beszel.git
synced 2026-10-22 06:12:33 +00:00
fix(agent): make package update checks work as the sandboxed agent user
This commit is contained in:
+67
-63
@@ -2,8 +2,10 @@ package agent
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -21,9 +23,6 @@ import (
|
||||
const (
|
||||
defaultPackageUpdatesInterval = time.Hour
|
||||
packageUpdatesTimeout = 5 * time.Minute
|
||||
// pacmanSyncInterval limits how often checkupdates downloads fresh sync
|
||||
// databases. Checks in between reuse the last synced copy.
|
||||
pacmanSyncInterval = 12 * time.Hour
|
||||
)
|
||||
|
||||
// packageUpdatesResult is the outcome of one package manager check.
|
||||
@@ -51,7 +50,8 @@ type packageUpdatesManager struct {
|
||||
|
||||
// newPackageUpdatesManager returns nil if disabled or no supported package manager
|
||||
// is found. Agents running in a container are skipped because the container's
|
||||
// package database is not the host's. dataDir holds pacman's private sync databases.
|
||||
// package database is not the host's. dataDir holds the package managers' per-user
|
||||
// caches and state, which they cannot write to their usual locations as the agent user.
|
||||
func newPackageUpdatesManager(dataDir string) *packageUpdatesManager {
|
||||
if runtime.GOOS != "linux" || runningInContainer() {
|
||||
return nil
|
||||
@@ -144,11 +144,11 @@ func detectPackageManager(dataDir string) (string, packageUpdatesCheck) {
|
||||
case commandExists("apt-get"):
|
||||
return "apt", checkApt
|
||||
case commandExists("dnf"):
|
||||
return "dnf", checkDnf
|
||||
return "dnf", newDnfCheck(dataDir)
|
||||
case commandExists("zypper"):
|
||||
return "zypper", checkZypper
|
||||
case commandExists("checkupdates"):
|
||||
return "pacman", newPacmanCheck(dataDir)
|
||||
return "zypper", newZypperCheck(dataDir)
|
||||
case commandExists("pacman"):
|
||||
return "pacman", checkPacman
|
||||
case commandExists("apk"):
|
||||
return "apk", checkApk
|
||||
}
|
||||
@@ -171,12 +171,18 @@ func runPackageCommandEnv(ctx context.Context, env []string, okCodes []int, name
|
||||
cmd := exec.CommandContext(ctx, name, args...)
|
||||
cmd.Env = append(os.Environ(), "LC_ALL=C")
|
||||
cmd.Env = append(cmd.Env, env...)
|
||||
// checkupdates is a shell script, so a timeout kills only the script and its
|
||||
// children can keep stdout open. WaitDelay stops Output from waiting on them.
|
||||
// a timeout kills only the command itself, and children it started can keep
|
||||
// stdout open. WaitDelay stops Output from waiting on them.
|
||||
cmd.WaitDelay = 10 * time.Second
|
||||
out, err := cmd.Output()
|
||||
if exitErr, ok := errors.AsType[*exec.ExitError](err); ok && slices.Contains(okCodes, exitErr.ExitCode()) {
|
||||
return string(out), nil
|
||||
if exitErr, ok := errors.AsType[*exec.ExitError](err); ok {
|
||||
if slices.Contains(okCodes, exitErr.ExitCode()) {
|
||||
return string(out), nil
|
||||
}
|
||||
// include stderr so a failure says why, not just "exit status 1"
|
||||
if stderr := strings.TrimSpace(string(exitErr.Stderr)); stderr != "" {
|
||||
err = fmt.Errorf("%s: %w: %s", name, err, stderr)
|
||||
}
|
||||
}
|
||||
return string(out), err
|
||||
}
|
||||
@@ -206,10 +212,26 @@ func checkApt(ctx context.Context) (packageUpdatesResult, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// newDnfCheck points dnf5's per-user state (log) and cache dirs into dataDir.
|
||||
// They default to $HOME/.local/state and $HOME/.cache, and the agent user's home
|
||||
// is usually /nonexistent. With -C, dnf5 still reads the system metadata cache.
|
||||
func newDnfCheck(dataDir string) packageUpdatesCheck {
|
||||
var env []string
|
||||
if dataDir != "" {
|
||||
env = []string{
|
||||
"XDG_STATE_HOME=" + filepath.Join(dataDir, "state"),
|
||||
"XDG_CACHE_HOME=" + filepath.Join(dataDir, "cache"),
|
||||
}
|
||||
}
|
||||
return func(ctx context.Context) (packageUpdatesResult, error) {
|
||||
return checkDnf(ctx, env)
|
||||
}
|
||||
}
|
||||
|
||||
// checkDnf uses the system metadata cache only (-C), so it never downloads metadata.
|
||||
// check-update lists only available versions, so installed versions come from rpm.
|
||||
func checkDnf(ctx context.Context) (packageUpdatesResult, error) {
|
||||
out, err := runPackageCommand(ctx, []int{100}, "dnf", "-q", "-C", "check-update")
|
||||
func checkDnf(ctx context.Context, env []string) (packageUpdatesResult, error) {
|
||||
out, err := runPackageCommandEnv(ctx, env, []int{100}, "dnf", "-q", "-C", "check-update")
|
||||
if err != nil {
|
||||
return packageUpdatesResult{}, err
|
||||
}
|
||||
@@ -229,7 +251,7 @@ func checkDnf(ctx context.Context) (packageUpdatesResult, error) {
|
||||
}
|
||||
}
|
||||
|
||||
out, err = runPackageCommand(ctx, []int{100}, "dnf", "-q", "-C", "check-update", "--security")
|
||||
out, err = runPackageCommandEnv(ctx, env, []int{100}, "dnf", "-q", "-C", "check-update", "--security")
|
||||
if err == nil {
|
||||
// --security lists the lowest version that fixes an advisory, which may be
|
||||
// older than the version check-update offers, so match on name.arch only
|
||||
@@ -253,63 +275,45 @@ func checkDnf(ctx context.Context) (packageUpdatesResult, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// checkZypper lists package updates. Security updates come from patches, which
|
||||
// newZypperCheck lists package updates. Security updates come from patches, which
|
||||
// zypper does not map to packages here, so only the security count is known.
|
||||
func checkZypper(ctx context.Context) (packageUpdatesResult, error) {
|
||||
out, err := runPackageCommand(ctx, nil, "zypper", "--no-refresh", "-q", "list-updates")
|
||||
if err != nil {
|
||||
return packageUpdatesResult{}, err
|
||||
}
|
||||
packages := parseZypperListUpdates(out)
|
||||
result := packageUpdatesResult{packages: packages, counts: []uint16{uint16(len(packages))}}
|
||||
out, err = runPackageCommand(ctx, nil, "zypper", "--no-refresh", "-q", "list-patches", "--category", "security")
|
||||
if err == nil {
|
||||
result.counts = append(result.counts, parseZypperTable(out))
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// newPacmanCheck uses checkupdates (pacman-contrib), which syncs a private copy of
|
||||
// the databases and never touches pacman's own. The copy lives in dataDir because
|
||||
// the systemd unit's ProtectSystem=strict makes the default /tmp location read-only.
|
||||
// It syncs every pacmanSyncInterval and uses the existing copy (-n) in between.
|
||||
// Local upgrades show up right away since checkupdates links the live local DB.
|
||||
// Exit code 2 means no updates.
|
||||
func newPacmanCheck(dataDir string) packageUpdatesCheck {
|
||||
var env []string
|
||||
var syncDir string
|
||||
// zypper rebuilds the solv cache from the raw metadata when it is missing or stale,
|
||||
// which the agent user cannot do in /var/cache/zypp, so it keeps its own in dataDir.
|
||||
func newZypperCheck(dataDir string) packageUpdatesCheck {
|
||||
args := []string{"--no-refresh", "-q"}
|
||||
if dataDir != "" {
|
||||
dbPath := filepath.Join(dataDir, "checkup-db")
|
||||
env = []string{"CHECKUPDATES_DB=" + dbPath}
|
||||
syncDir = filepath.Join(dbPath, "sync")
|
||||
args = append(args, "--solv-cache-dir", filepath.Join(dataDir, "zypp-solv"))
|
||||
}
|
||||
// checks never overlap (packageUpdatesManager.running), so no lock is needed
|
||||
var lastSync time.Time
|
||||
return func(ctx context.Context) (packageUpdatesResult, error) {
|
||||
// -n with a missing database reports no updates rather than failing,
|
||||
// so always sync first and whenever the private copy is missing
|
||||
sync := lastSync.IsZero() || time.Since(lastSync) >= pacmanSyncInterval
|
||||
if !sync && syncDir != "" {
|
||||
if _, err := os.Stat(syncDir); err != nil {
|
||||
sync = true
|
||||
}
|
||||
}
|
||||
var args []string
|
||||
if !sync {
|
||||
args = append(args, "-n")
|
||||
}
|
||||
out, err := runPackageCommandEnv(ctx, env, []int{2}, "checkupdates", args...)
|
||||
out, err := runPackageCommand(ctx, nil, "zypper", slices.Concat(args, []string{"list-updates"})...)
|
||||
if err != nil {
|
||||
return packageUpdatesResult{}, err
|
||||
}
|
||||
if sync {
|
||||
lastSync = time.Now()
|
||||
packages := parseZypperListUpdates(out)
|
||||
result := packageUpdatesResult{packages: packages, counts: []uint16{uint16(len(packages))}}
|
||||
out, err = runPackageCommand(ctx, nil, "zypper", slices.Concat(args, []string{"list-patches", "--category", "security"})...)
|
||||
if err == nil {
|
||||
result.counts = append(result.counts, parseZypperTable(out))
|
||||
}
|
||||
packages := parsePacmanCheckUpdates(out)
|
||||
return packageUpdatesResult{counts: []uint16{uint16(len(packages))}, packages: packages}, nil
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
// checkPacman compares installed packages against the system sync databases.
|
||||
// It never syncs them; pacman -Sy(u) by the user or a timer does that.
|
||||
func checkPacman(ctx context.Context) (packageUpdatesResult, error) {
|
||||
out, err := runPackageCommand(ctx, nil, "pacman", "-Qu")
|
||||
// -Qu exits 1 with no output when nothing is upgradable; errors print to stderr
|
||||
if exitErr, ok := errors.AsType[*exec.ExitError](err); ok && exitErr.ExitCode() == 1 && len(bytes.TrimSpace(exitErr.Stderr)) == 0 {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return packageUpdatesResult{}, err
|
||||
}
|
||||
packages := parsePacmanQueryUpgrades(out)
|
||||
return packageUpdatesResult{counts: []uint16{uint16(len(packages))}, packages: packages}, nil
|
||||
}
|
||||
|
||||
func checkApk(ctx context.Context) (packageUpdatesResult, error) {
|
||||
out, err := runPackageCommand(ctx, nil, "apk", "--no-network", "-u", "list")
|
||||
if err != nil {
|
||||
@@ -462,8 +466,8 @@ func parseZypperListUpdates(out string) (packages []system.PackageUpdate) {
|
||||
return packages
|
||||
}
|
||||
|
||||
// parsePacmanCheckUpdates parses "name old -> new" lines.
|
||||
func parsePacmanCheckUpdates(out string) (packages []system.PackageUpdate) {
|
||||
// parsePacmanQueryUpgrades parses "name old -> new" lines, which may end in "[ignored]".
|
||||
func parsePacmanQueryUpgrades(out string) (packages []system.PackageUpdate) {
|
||||
scanner := bufio.NewScanner(strings.NewReader(out))
|
||||
for scanner.Scan() {
|
||||
fields := strings.Fields(scanner.Text())
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -218,7 +217,7 @@ exit 100`,
|
||||
"rpm": `cat "` + testDataPath(t, tt.installed) + `"
|
||||
exit 1`,
|
||||
})
|
||||
result, err := checkDnf(context.Background())
|
||||
result, err := newDnfCheck(t.TempDir())(context.Background())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []uint16{uint16(tt.total), uint16(tt.securityCount)}, result.counts)
|
||||
assert.True(t, result.securityKnown)
|
||||
@@ -237,12 +236,36 @@ echo "bash.x86_64 5.1.8-9.el9 baseos"
|
||||
exit 100`,
|
||||
"rpm": `echo "bash.x86_64 5.1.8-6.el9_1"`,
|
||||
})
|
||||
result, err := checkDnf(context.Background())
|
||||
result, err := newDnfCheck(t.TempDir())(context.Background())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []uint16{1}, result.counts)
|
||||
assert.False(t, result.securityKnown)
|
||||
assert.Equal(t, []system.PackageUpdate{{Name: "bash", Current: "5.1.8-6.el9_1", Available: "5.1.8-9.el9"}}, result.packages)
|
||||
})
|
||||
|
||||
t.Run("error includes stderr", func(t *testing.T) {
|
||||
fakeCommands(t, map[string]string{
|
||||
"dnf": `echo "Error: Cache-only enabled but no cache for 'baseos'" >&2
|
||||
exit 1`,
|
||||
})
|
||||
_, err := newDnfCheck(t.TempDir())(context.Background())
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, "dnf: exit status 1: Error: Cache-only enabled but no cache for 'baseos'", err.Error())
|
||||
})
|
||||
|
||||
t.Run("state and cache in data dir", func(t *testing.T) {
|
||||
dataDir := t.TempDir()
|
||||
envFile := filepath.Join(t.TempDir(), "env")
|
||||
fakeCommands(t, map[string]string{
|
||||
"dnf": `echo "$XDG_STATE_HOME $XDG_CACHE_HOME" >> "` + envFile + `"`,
|
||||
})
|
||||
_, err := newDnfCheck(dataDir)(context.Background())
|
||||
require.NoError(t, err)
|
||||
env, err := os.ReadFile(envFile)
|
||||
require.NoError(t, err)
|
||||
want := filepath.Join(dataDir, "state") + " " + filepath.Join(dataDir, "cache") + "\n"
|
||||
assert.Equal(t, want+want, string(env))
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseZypperTable(t *testing.T) {
|
||||
@@ -273,25 +296,36 @@ func TestParseZypperListUpdates(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCheckZypper(t *testing.T) {
|
||||
dataDir := t.TempDir()
|
||||
argsFile := filepath.Join(t.TempDir(), "args")
|
||||
fakeCommands(t, map[string]string{
|
||||
"zypper": `case "$*" in *list-patches*) cat "` + testDataPath(t, "zypper_leap155_list_patches_security.txt") + `" ;; *) cat "` + testDataPath(t, "zypper_leap155_list_updates.txt") + `" ;; esac`,
|
||||
"zypper": `echo "$*" >> "` + argsFile + `"
|
||||
case "$*" in *list-patches*) cat "` + testDataPath(t, "zypper_leap155_list_patches_security.txt") + `" ;; *) cat "` + testDataPath(t, "zypper_leap155_list_updates.txt") + `" ;; esac`,
|
||||
})
|
||||
result, err := checkZypper(context.Background())
|
||||
result, err := newZypperCheck(dataDir)(context.Background())
|
||||
require.NoError(t, err)
|
||||
// security patches don't map to packages, so only the count is known
|
||||
assert.Equal(t, []uint16{22, 4}, result.counts)
|
||||
assert.False(t, result.securityKnown)
|
||||
assert.Len(t, result.packages, 22)
|
||||
|
||||
// both commands use the agent's own solv cache
|
||||
args, err := os.ReadFile(argsFile)
|
||||
require.NoError(t, err)
|
||||
solvDir := filepath.Join(dataDir, "zypp-solv")
|
||||
assert.Equal(t, "--no-refresh -q --solv-cache-dir "+solvDir+" list-updates\n"+
|
||||
"--no-refresh -q --solv-cache-dir "+solvDir+" list-patches --category security\n", string(args))
|
||||
}
|
||||
|
||||
func TestParsePacmanCheckUpdates(t *testing.T) {
|
||||
func TestParsePacmanQueryUpgrades(t *testing.T) {
|
||||
assert.Equal(t, []system.PackageUpdate{
|
||||
{Name: "libpcap", Current: "1.10.7-1", Available: "1.11.0-1"},
|
||||
{Name: "libsecret", Current: "0.21.7-1", Available: "0.21.8.2-1"},
|
||||
{Name: "libtirpc", Current: "1.3.7-1", Available: "1.3.8-1"},
|
||||
{Name: "tzdata", Current: "2026c-1", Available: "2026d-1"},
|
||||
}, parsePacmanCheckUpdates(readPackageUpdatesTestData(t, "pacman_checkupdates.txt")))
|
||||
assert.Empty(t, parsePacmanCheckUpdates(""))
|
||||
}, parsePacmanQueryUpgrades(readPackageUpdatesTestData(t, "pacman_query_upgrades.txt")))
|
||||
assert.Empty(t, parsePacmanQueryUpgrades(""))
|
||||
assert.Equal(t, []system.PackageUpdate{{Name: "linux", Current: "6.1-1", Available: "6.2-1"}}, parsePacmanQueryUpgrades("linux 6.1-1 -> 6.2-1 [ignored]\n"))
|
||||
}
|
||||
|
||||
func TestParseApkUpgradable(t *testing.T) {
|
||||
@@ -400,46 +434,28 @@ func TestGetPackageUpdatesHandler(t *testing.T) {
|
||||
assert.Equal(t, system.PackageUpdates{Manager: "apk", CheckedAt: 1700000000, Packages: packages}, sent)
|
||||
}
|
||||
|
||||
func TestPacmanCheckSync(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("requires a shell script on PATH")
|
||||
func TestCheckPacman(t *testing.T) {
|
||||
tests := []struct {
|
||||
name, script string
|
||||
counts []uint16
|
||||
err string
|
||||
}{
|
||||
{"updates", `echo "linux 6.1-1 -> 6.2-1"`, []uint16{1}, ""},
|
||||
{"no updates", "exit 1", []uint16{0}, ""},
|
||||
{"error", `echo "error: failed to initialize alpm library" >&2
|
||||
exit 1`, nil, "pacman: exit status 1: error: failed to initialize alpm library"},
|
||||
}
|
||||
binDir := t.TempDir()
|
||||
dataDir := t.TempDir()
|
||||
logFile := filepath.Join(binDir, "calls.log")
|
||||
// fake checkupdates logs its args and db path, and creates the sync dir when syncing
|
||||
script := `#!/bin/sh
|
||||
echo "args=[$*] db=$CHECKUPDATES_DB" >> ` + logFile + `
|
||||
[ "$1" = "-n" ] || mkdir -p "$CHECKUPDATES_DB/sync"
|
||||
echo "linux 6.1-1 -> 6.2-1"
|
||||
`
|
||||
require.NoError(t, os.WriteFile(filepath.Join(binDir, "checkupdates"), []byte(script), 0o755))
|
||||
t.Setenv("PATH", binDir+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||||
|
||||
check := newPacmanCheck(dataDir)
|
||||
dbPath := filepath.Join(dataDir, "checkup-db")
|
||||
readCalls := func() []string {
|
||||
data, err := os.ReadFile(logFile)
|
||||
require.NoError(t, err)
|
||||
return strings.Split(strings.TrimSpace(string(data)), "\n")
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fakeCommands(t, map[string]string{"pacman": `[ "$*" = "-Qu" ] || exit 9
|
||||
` + tt.script})
|
||||
result, err := checkPacman(context.Background())
|
||||
if tt.err != "" {
|
||||
require.EqualError(t, err, tt.err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.counts, result.counts)
|
||||
})
|
||||
}
|
||||
|
||||
// first check syncs
|
||||
result, err := check(context.Background())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []uint16{1}, result.counts)
|
||||
assert.Equal(t, []system.PackageUpdate{{Name: "linux", Current: "6.1-1", Available: "6.2-1"}}, result.packages)
|
||||
// later checks reuse the synced copy
|
||||
_, err = check(context.Background())
|
||||
require.NoError(t, err)
|
||||
// a missing private copy forces a sync
|
||||
require.NoError(t, os.RemoveAll(dbPath))
|
||||
_, err = check(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, []string{
|
||||
"args=[] db=" + dbPath,
|
||||
"args=[-n] db=" + dbPath,
|
||||
"args=[] db=" + dbPath,
|
||||
}, readCalls())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
acl 2.3.2-2 -> 2.4.0-1
|
||||
archlinux-keyring 20260409-1 -> 20260909-1
|
||||
attr 2.5.2-2 -> 2.6.0-1
|
||||
audit 4.1.4-2 -> 4.2.1-1
|
||||
bash 5.3.9-1 -> 5.3.20-1
|
||||
binutils 2.46-1 -> 2.47-4
|
||||
ca-certificates-mozilla 3.123-1 -> 3.130-1
|
||||
coreutils 9.10-1 -> 9.12-2
|
||||
cryptsetup 2.8.6-1 -> 2.8.8-1
|
||||
curl 8.19.0-1 -> 8.22.0-1
|
||||
device-mapper 2.03.39-1 -> 2.03.43-2
|
||||
ex-vi-compat 2-1 -> 3-1
|
||||
expat 2.7.5-1 -> 2.8.5-1
|
||||
file 5.47-3 -> 5.48-1
|
||||
findutils 4.10.0-3 -> 4.11.0-1
|
||||
gawk 5.4.0-1 -> 5.4.1-1
|
||||
gcc-libs 15.2.1+r604+g0b99615a8aef-1 -> 16.2.1+r23+gd564253eb6c8-1
|
||||
glib2 2.88.0-1 -> 2.88.3-1
|
||||
glibc 2.43+r5+g856c426a7534-1 -> 2.44+r50+g1848099f063e-1
|
||||
gnupg 2.4.9-1 -> 2.4.9-3
|
||||
gnutls 3.8.12-2 -> 3.8.13-3
|
||||
gpgme 2.0.1-3 -> 2.2.0-1
|
||||
gpm 1.20.7.r38.ge82d1a6-6 -> 1.20.7.r38.ge82d1a6-7
|
||||
gzip 1.14-2 -> 1.15-1
|
||||
hwdata 0.406-1 -> 0.411-1
|
||||
iana-etc 20260409-1 -> 20260617-1
|
||||
inetutils 2.7-2 -> 2.8-1
|
||||
iproute2 7.0.0-1 -> 7.2.0-1
|
||||
jansson 2.15.0-1 -> 2.15.1-1
|
||||
json-c 0.18-2 -> 0.19-1
|
||||
kbd 2.9.0-1 -> 2.10.0-1
|
||||
krb5 1.21.3-2 -> 1.22.2-1
|
||||
leancrypto 1.7.2-1 -> 1.9.1-1
|
||||
libarchive 3.8.7-1 -> 3.8.9-1
|
||||
libasan 15.2.1+r604+g0b99615a8aef-1 -> 16.2.1+r23+gd564253eb6c8-1
|
||||
libatomic 15.2.1+r604+g0b99615a8aef-1 -> 16.2.1+r23+gd564253eb6c8-1
|
||||
libbpf 1.6.2-1 -> 1.7.0-1
|
||||
libcap-ng 0.9.3-1 -> 0.9.6-1
|
||||
libedit 20251016_3.1-1 -> 20260512_3.1-1
|
||||
libelf 0.194-2 -> 0.196-1
|
||||
libevent 2.1.12-5 -> 2.1.13-2
|
||||
libffi 3.5.2-1 -> 3.8.0-1
|
||||
libgcc 15.2.1+r604+g0b99615a8aef-1 -> 16.2.1+r23+gd564253eb6c8-1
|
||||
libgcrypt 1.12.2-1 -> 1.12.4-1
|
||||
libgfortran 15.2.1+r604+g0b99615a8aef-1 -> 16.2.1+r23+gd564253eb6c8-1
|
||||
libgomp 15.2.1+r604+g0b99615a8aef-1 -> 16.2.1+r23+gd564253eb6c8-1
|
||||
libgpg-error 1.59-1 -> 1.61-1
|
||||
libksba 1.6.8-1 -> 1.8.1-1
|
||||
libldap 2.6.13-1 -> 2.7.1-1
|
||||
liblsan 15.2.1+r604+g0b99615a8aef-1 -> 16.2.1+r23+gd564253eb6c8-1
|
||||
libmakepkg-dropins 20-1 -> 20-2
|
||||
libnetfilter_conntrack 1.0.9-2 -> 1.1.1-1
|
||||
libnftnl 1.3.1-1 -> 1.3.2-1
|
||||
libnghttp2 1.69.0-1 -> 1.70.0-1
|
||||
libnghttp3 1.15.0-1 -> 1.18.0-1
|
||||
libngtcp2 1.22.1-1 -> 1.25.0-1
|
||||
libnsl 2.0.1-1 -> 2.0.1-2
|
||||
libobjc 15.2.1+r604+g0b99615a8aef-1 -> 16.2.1+r23+gd564253eb6c8-1
|
||||
libp11-kit 0.26.2-1 -> 0.26.5-1
|
||||
libpcap 1.10.6-1 -> 1.11.0-1
|
||||
libquadmath 15.2.1+r604+g0b99615a8aef-1 -> 16.2.1+r23+gd564253eb6c8-1
|
||||
libsecret 0.21.7-1 -> 0.21.8.2-1
|
||||
libssh2 1.11.1-1 -> 1.11.1-7
|
||||
libstdc++ 15.2.1+r604+g0b99615a8aef-1 -> 16.2.1+r23+gd564253eb6c8-1
|
||||
libsysprof-capture 50.0-1 -> 50.0-6
|
||||
libtirpc 1.3.7-1 -> 1.3.8-1
|
||||
libtsan 15.2.1+r604+g0b99615a8aef-1 -> 16.2.1+r23+gd564253eb6c8-1
|
||||
libubsan 15.2.1+r604+g0b99615a8aef-1 -> 16.2.1+r23+gd564253eb6c8-1
|
||||
libunistring 1.4.1-1 -> 1.4.2-1
|
||||
libusb 1.0.29-1 -> 1.0.30-1
|
||||
libxml2 2.15.3-1 -> 2.15.4-1
|
||||
linux-api-headers 6.19-1 -> 7.2-1
|
||||
nano 9.0-1 -> 9.2-1
|
||||
nettle 3.10.2-1 -> 4.0-1
|
||||
nftables 1:1.1.6-3 -> 1:1.1.7-3
|
||||
openssh 10.3p1-1 -> 10.5p1-1
|
||||
openssl 3.6.2-1 -> 3.6.5-1
|
||||
p11-kit 0.26.2-1 -> 0.26.5-1
|
||||
pacman 7.1.0.r9.g54d9411-1 -> 7.1.0.r9.g54d9411-2
|
||||
pacman-mirrorlist 20260406-1 -> 20260610-1
|
||||
pam 1.7.2-2 -> 1.7.3-1
|
||||
pambase 20250719-1 -> 20260616-1
|
||||
pcre2 10.47-1 -> 10.49-1
|
||||
pinentry 1.3.2-2 -> 1.3.3-1
|
||||
procps-ng 4.0.6-1 -> 4.0.7-1
|
||||
readline 8.3.003-1 -> 8.3.6-1
|
||||
sed 4.9-3 -> 4.10-2
|
||||
shadow 4.18.0-1 -> 4.20.0.arch1-1
|
||||
sqlite 3.53.0-2 -> 3.53.4-1
|
||||
systemd 260.1-1 -> 262-1
|
||||
systemd-libs 260.1-1 -> 262-1
|
||||
systemd-sysvcompat 260.1-1 -> 262-1
|
||||
tar 1.35-2 -> 1.35-5
|
||||
tpm2-tss 4.1.3-1 -> 4.2.0-2
|
||||
tzdata 2026a-1 -> 2026e-1
|
||||
util-linux 2.42-1 -> 2.42.4-1
|
||||
util-linux-libs 2.42-1 -> 2.42.4-1
|
||||
vim 9.2.0357-1 -> 9.2.1046-1
|
||||
vim-runtime 9.2.0357-1 -> 9.2.1046-1
|
||||
xz 5.8.3-1 -> 5.8.4-1
|
||||
@@ -20,6 +20,7 @@ ProtectHome=read-only
|
||||
ProtectHostname=yes
|
||||
ProtectKernelLogs=yes
|
||||
ProtectSystem=strict
|
||||
PrivateTmp=yes
|
||||
RemoveIPC=yes
|
||||
RestrictSUIDSGID=true
|
||||
|
||||
|
||||
@@ -1369,6 +1369,7 @@ ProtectHome=read-only
|
||||
ProtectHostname=yes
|
||||
ProtectKernelLogs=yes
|
||||
ProtectSystem=strict
|
||||
PrivateTmp=yes
|
||||
RemoveIPC=yes
|
||||
RestrictSUIDSGID=true
|
||||
|
||||
@@ -1396,6 +1397,13 @@ EOF
|
||||
sed -i '/^User=beszel$/a SupplementaryGroups=systemd-journal' /etc/systemd/system/beszel-agent.service
|
||||
fi
|
||||
|
||||
# ProtectSystem=strict makes /var/tmp read-only, but dnf4 and zypper run as a non-root
|
||||
# user keep temporary files in /var/tmp, so package update checks fail without this.
|
||||
if grep -q '^ProtectSystem=strict$' /etc/systemd/system/beszel-agent.service && ! grep -q '^PrivateTmp=' /etc/systemd/system/beszel-agent.service; then
|
||||
echo "Giving the agent service a private writable /tmp and /var/tmp"
|
||||
sed -i '/^ProtectSystem=strict$/a PrivateTmp=yes' /etc/systemd/system/beszel-agent.service
|
||||
fi
|
||||
|
||||
# Load and start the service
|
||||
printf "\nLoading and starting the agent service...\n"
|
||||
systemctl daemon-reload
|
||||
|
||||
Reference in New Issue
Block a user