feat(platform): add node orchestration and the felis entrypoint

The platform package that places servers across nodes and wires the operator, build, restore, and reaper subsystems, plus cmd/felis, the single binary that runs them.
This commit is contained in:
flyemoji committed 2026-06-26 23:32:38 +09:00
1 parent b508fccc6f
commit 47fcd90f75
21 files changed
+3713

No files matched your search

+140
View File
@@ -0,0 +1,140 @@
package main
import (
"bytes"
"context"
"os"
"path/filepath"
"strings"
"testing"
"felis.lolicon.best/internal/backup"
)
// archiveTempWorld tars a freshly populated world directory with the real
// TarLocal and returns the absolute archive ref plus the backup root it lives
// under, so the restore round-trip below exercises production code end to end
// without a cluster.
func archiveTempWorld(t *testing.T, files map[string]string) (ref string, backupRoot string) {
t.Helper()
srcDir := t.TempDir()
for name, content := range files {
full := filepath.Join(srcDir, filepath.FromSlash(name))
if err := os.MkdirAll(filepath.Dir(full), 0o750); err != nil {
t.Fatalf("mkdir: %v", err)
}
if err := os.WriteFile(full, []byte(content), 0o640); err != nil {
t.Fatalf("write: %v", err)
}
}
backupRoot = t.TempDir()
ar := &backup.TarLocal{
BackupRoot: backupRoot,
Resolve: func(string) (string, error) { return srcDir, nil },
}
got, _, err := ar.Archive(context.Background(), "survival", "world-survival-0")
if err != nil {
t.Fatalf("Archive: %v", err)
}
return string(got), backupRoot
}
// The restore subcommand must extract the archived world into the target world
// mount — the real reverse of what the reaper wrote.
func TestRestoreSubcommandRoundTrips(t *testing.T) {
files := map[string]string{
"level.dat": "world-seed",
"region/r.0.0.mca": "chunk-bytes",
"playerdata/uuid.json": "{}",
}
ref, backupRoot := archiveTempWorld(t, files)
// Seed the target with a stale file the archive does not contain: the restore
// must prune it (replace semantics), not leave it behind. This is the path the
// admin/owner rollback-onto-a-stopped-populated-PVC case actually takes.
targetDir := t.TempDir()
stale := filepath.Join(targetDir, "region", "r.9.9.mca")
if err := os.MkdirAll(filepath.Dir(stale), 0o750); err != nil {
t.Fatalf("mkdir stale: %v", err)
}
if err := os.WriteFile(stale, []byte("griefer-chunk"), 0o640); err != nil {
t.Fatalf("seed stale: %v", err)
}
var out, errBuf bytes.Buffer
code := run([]string{
"restore",
"--server", "survival",
"--ref", ref,
"--archive-store", "tarLocal",
"--backup-root", backupRoot,
"--worlds-root", targetDir,
}, &out, &errBuf)
if code != 0 {
t.Fatalf("restore exit = %d, stderr=%q", code, errBuf.String())
}
for name, want := range files {
full := filepath.Join(targetDir, filepath.FromSlash(name))
got, err := os.ReadFile(full)
if err != nil {
t.Errorf("restored file %q missing: %v", name, err)
continue
}
if string(got) != want {
t.Errorf("restored %q = %q, want %q", name, got, want)
}
}
if _, err := os.Stat(stale); !os.IsNotExist(err) {
t.Errorf("stale file survived restore (err=%v); replace semantics broken", err)
}
}
// A ref outside the backup mount must be rejected before any file is opened —
// the restore Pod must never read an arbitrary host path.
func TestRestoreSubcommandRejectsRefOutsideBackupRoot(t *testing.T) {
backupRoot := t.TempDir()
outside := filepath.Join(t.TempDir(), "evil.tar.gz")
var out, errBuf bytes.Buffer
code := run([]string{
"restore",
"--server", "survival",
"--ref", outside,
"--backup-root", backupRoot,
"--worlds-root", t.TempDir(),
}, &out, &errBuf)
if code != 1 {
t.Fatalf("exit = %d, want 1 for an out-of-root ref", code)
}
if !strings.Contains(errBuf.String(), "not under backup root") {
t.Errorf("expected containment error, got %q", errBuf.String())
}
}
// An unimplemented archive store must fail loudly, not silently no-op.
func TestRestoreSubcommandRejectsUnknownStore(t *testing.T) {
var out, errBuf bytes.Buffer
code := run([]string{
"restore",
"--ref", "/backups/x.tar.gz",
"--archive-store", "s3snapshot",
"--backup-root", "/backups",
}, &out, &errBuf)
if code != 1 {
t.Fatalf("exit = %d, want 1 for an unknown store", code)
}
if !strings.Contains(errBuf.String(), "not implemented") {
t.Errorf("expected not-implemented error, got %q", errBuf.String())
}
}
// --ref is mandatory: a missing ref is a usage error, not a panic.
func TestRestoreSubcommandRequiresRef(t *testing.T) {
var out, errBuf bytes.Buffer
code := run([]string{"restore", "--backup-root", "/backups"}, &out, &errBuf)
if code != 2 {
t.Fatalf("exit = %d, want 2 for a missing --ref", code)
}
}