fix(workloads): world executors run as root so game-image worlds are readable

A live backup drill on test-one failed: 'tar walk: open
/world/world/level.dat: permission denied'. The world volume belongs to
the game image's own UID (root for every Paper image we ship), and Paper
saves level.dat mode 0600 — a fixed uid-1000 executor can neither read
it (backup/reaper archive) nor overwrite it (restore). The same identity
silently broke on-demand backups, restores, and the reaper for every
server that had saved once.

Run the backup Job, restore Job, file Job, and the reaper pod as root
with DAC_OVERRIDE on top of drop-ALL — the same owner-matching precedent
as the operator's forwarding-init container; DAC_OVERRIDE extends it to
game images whose UID is neither root nor ours. FSGroup is omitted when
zero so a root executor never chgrps the world volume. Shape tests
updated for the new identity.
This commit is contained in:
Lemon-miaow committed 2026-09-23 06:20:07 +08:00
1 parent f21aef3cfa
commit 2010961d32
12 files changed
+223 -105

No files matched your search

+8 -13
View File
@@ -87,9 +87,14 @@ type Config struct {
// CPULimit / MemLimit cap the backup container.
CPULimit string
MemLimit string
// RunAsUser / RunAsGroup / FSGroup are the Pod's runtime identity. FSGroup MUST
// match the operator StatefulSet's runtime group so the read-only world mount is
// readable by this Pod's uid.
// RunAsUser / RunAsGroup / FSGroup are the Pod's runtime identity. They default
// to ROOT (0:0) for the same reason the operator's forwarding-init container
// runs as root: the world volume is written by the game image's own UID (root
// for every Paper image we ship), and Paper saves files a non-root uid can
// never read — level.dat is written mode 0600 (tar walk: permission denied,
// verified live). DAC_OVERRIDE on the container covers images whose UID is
// neither root nor ours. Set 0/0/0 explicitly for root; FSGroup is omitted
// when zero.
RunAsUser int64
RunAsGroup int64
FSGroup int64
@@ -111,7 +116,6 @@ const (
defaultDeadline = 30 * time.Minute
defaultCPULimit = "1"
defaultMemLimit = "1Gi"
defaultRunAsID = int64(1000)
defaultTTL = 10 * time.Minute
)
@@ -145,15 +149,6 @@ func (c Config) withDefaults() Config {
if c.MemLimit == "" {
c.MemLimit = defaultMemLimit
}
if c.RunAsUser == 0 {
c.RunAsUser = defaultRunAsID
}
if c.RunAsGroup == 0 {
c.RunAsGroup = defaultRunAsID
}
if c.FSGroup == 0 {
c.FSGroup = defaultRunAsID
}
if c.TTLAfterFinished <= 0 {
c.TTLAfterFinished = defaultTTL
}