feat(builder): boots as nodes run them, fixed-size model requests, safety check, repairs #25
No files matched your search
@@ -30,7 +30,9 @@ func TestOutsideDirsSkipsSteamNoise(t *testing.T) {
|
|||||||
r := &bootReport{ContainerChanges: []string{
|
r := &bootReport{ContainerChanges: []string{
|
||||||
"A /home/steam/.config/example-game/worlds/w.db",
|
"A /home/steam/.config/example-game/worlds/w.db",
|
||||||
"A /home/steam/Steam/logs/connection_log.txt",
|
"A /home/steam/Steam/logs/connection_log.txt",
|
||||||
|
"C /home/steam/Steam/config/config.vdf",
|
||||||
"C /home/steam/.steam/registry.vdf",
|
"C /home/steam/.steam/registry.vdf",
|
||||||
|
"A /usr/sbin/docker-init",
|
||||||
}}
|
}}
|
||||||
got := outsideDirs(r)
|
got := outsideDirs(r)
|
||||||
if len(got) != 1 || got[0] != "/home/steam/.config/example-game/worlds" {
|
if len(got) != 1 || got[0] != "/home/steam/.config/example-game/worlds" {
|
||||||
|
|||||||
+2
-11
@@ -336,9 +336,6 @@ func closestVar(name string, vars []string) string {
|
|||||||
return best
|
return best
|
||||||
}
|
}
|
||||||
|
|
||||||
// Files the Steam client, Proton and the OS write for themselves: not the game's data
|
|
||||||
var outsideNoise = []string{"/Steam/logs/", "/.steam/", "/steamcmd/", "/.cache/", "/tmp/", "/.local/share/umu"}
|
|
||||||
|
|
||||||
// savesOutsideVolumes turns first boot's "written outside the volumes" into a hard check: anything the
|
// savesOutsideVolumes turns first boot's "written outside the volumes" into a hard check: anything the
|
||||||
// game itself wrote there (worlds or player lists under the user's home folder) is lost every time the
|
// game itself wrote there (worlds or player lists under the user's home folder) is lost every time the
|
||||||
// node recreates the container. A template passes once its startup command or settings point the game at
|
// node recreates the container. A template passes once its startup command or settings point the game at
|
||||||
@@ -372,14 +369,8 @@ func outsideDirs(report *bootReport) []string {
|
|||||||
}
|
}
|
||||||
dirs := map[string]bool{}
|
dirs := map[string]bool{}
|
||||||
for _, c := range report.ContainerChanges {
|
for _, c := range report.ContainerChanges {
|
||||||
_, p, _ := strings.Cut(c, " ")
|
// A report saved by an older version can still list files that aren't the game's state
|
||||||
noise := false
|
if _, p, _ := strings.Cut(c, " "); p != "" && !notGameState(p) {
|
||||||
for _, n := range outsideNoise {
|
|
||||||
if strings.Contains(p, n) {
|
|
||||||
noise = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !noise && p != "" {
|
|
||||||
dirs[path.Dir(p)] = true
|
dirs[path.Dir(p)] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+49
-29
@@ -512,16 +512,36 @@ func (b *booter) fileLines(report *bootReport) []logLine {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// System folders and files that aren't the game's state: what's written there doesn't matter when lost
|
// Files that aren't the game's state, so losing them doesn't matter: system folders, Docker's own init
|
||||||
|
// (in every container started with --init), logs, lock and pid files, and what the Steam client, Proton
|
||||||
|
// and the OS keep for themselves in the home folder
|
||||||
var (
|
var (
|
||||||
systemPrefixes = []string{"/tmp", "/proc", "/sys", "/dev", "/run", "/etc", "/var/log", "/var/cache", "/var/tmp", "/var/lib/apt"}
|
systemPrefixes = []string{"/tmp", "/proc", "/sys", "/dev", "/run", "/etc", "/var/log", "/var/cache", "/var/tmp", "/var/lib/apt", "/usr/sbin/docker-init"}
|
||||||
transientExt = map[string]bool{".log": true, ".pid": true, ".lock": true, ".tmp": true, ".swp": true}
|
transientExt = map[string]bool{".log": true, ".pid": true, ".lock": true, ".tmp": true, ".swp": true}
|
||||||
|
clientNoise = []string{"/Steam/logs/", "/Steam/config/", "/Steam/appcache/", "/.steam/", "/steamcmd/", "/.cache/", "/tmp/", "/.local/share/umu"}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func notGameState(p string) bool {
|
||||||
|
if transientExt[strings.ToLower(path.Ext(p))] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, prefix := range systemPrefixes {
|
||||||
|
if p == prefix || strings.HasPrefix(p, prefix+"/") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, n := range clientNoise {
|
||||||
|
if strings.Contains(p, n) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// containerChanges lists the files the server wrote anywhere but /server and /data. All of them are
|
// containerChanges lists the files the server wrote anywhere but /server and /data. All of them are
|
||||||
// lost when the node recreates the container (every start): in the container's own filesystem, and in
|
// lost when the node recreates the container (every start): in the container's own filesystem, and in
|
||||||
// the image's VOLUME folders, which Docker gives fresh anonymous volumes each time. Logs, lock and pid
|
// the image's VOLUME folders, which Docker gives fresh anonymous volumes each time. Files that aren't
|
||||||
// files are left out; saves of any kind are not.
|
// the game's state are left out; saves of any kind are not.
|
||||||
func (b *booter) containerChanges() []string {
|
func (b *booter) containerChanges() []string {
|
||||||
var changes []string
|
var changes []string
|
||||||
if out, err := exec.Command("docker", "diff", b.container).Output(); err == nil {
|
if out, err := exec.Command("docker", "diff", b.container).Output(); err == nil {
|
||||||
@@ -541,7 +561,7 @@ func (b *booter) containerChanges() []string {
|
|||||||
n := 0
|
n := 0
|
||||||
for _, f := range strings.Split(strings.TrimSpace(string(files)), "\n") {
|
for _, f := range strings.Split(strings.TrimSpace(string(files)), "\n") {
|
||||||
rel := strings.TrimPrefix(f, "/v/")
|
rel := strings.TrimPrefix(f, "/v/")
|
||||||
if f == "" || transientExt[strings.ToLower(path.Ext(rel))] {
|
if f == "" || notGameState(path.Join(dest, rel)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if n++; n > 200 {
|
if n++; n > 200 {
|
||||||
@@ -553,42 +573,42 @@ func (b *booter) containerChanges() []string {
|
|||||||
return changes
|
return changes
|
||||||
}
|
}
|
||||||
|
|
||||||
// outsideFiles picks the files out of `docker diff` output: added or changed leaves (docker diff lists
|
// outsideFiles picks the game's files out of `docker diff` output: added or changed outside the mounted
|
||||||
// folders too) outside the mounted volumes and system folders, without transient files.
|
// volumes. docker diff lists folders too, as changed when anything inside them changed, so a folder only
|
||||||
|
// counts when nothing inside it did (it was created empty).
|
||||||
func outsideFiles(diff string, mounts map[string]string) []string {
|
func outsideFiles(diff string, mounts map[string]string) []string {
|
||||||
var paths []string
|
|
||||||
kinds := map[string]string{}
|
kinds := map[string]string{}
|
||||||
|
parents := map[string]bool{}
|
||||||
for _, line := range strings.Split(strings.TrimSpace(diff), "\n") {
|
for _, line := range strings.Split(strings.TrimSpace(diff), "\n") {
|
||||||
kind, p, ok := strings.Cut(strings.TrimSpace(line), " ")
|
kind, p, ok := strings.Cut(strings.TrimSpace(line), " ")
|
||||||
if !ok || kind == "D" || p == "" {
|
if !ok || p == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
skip := transientExt[strings.ToLower(path.Ext(p))]
|
kinds[p] = kind
|
||||||
for _, prefix := range systemPrefixes {
|
for d := path.Dir(p); d != "/" && d != "."; d = path.Dir(d) {
|
||||||
if p == prefix || strings.HasPrefix(p, prefix+"/") {
|
parents[d] = true
|
||||||
skip = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var files []string
|
||||||
|
for p, kind := range kinds {
|
||||||
|
if kind != "D" && !parents[p] && !notGameState(p) && !inMounts(p, mounts) {
|
||||||
|
files = append(files, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(files)
|
||||||
|
for i, p := range files {
|
||||||
|
files[i] = kinds[p] + " " + p
|
||||||
|
}
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
|
func inMounts(p string, mounts map[string]string) bool {
|
||||||
for mount := range mounts {
|
for mount := range mounts {
|
||||||
if p == mount || strings.HasPrefix(p, mount+"/") {
|
if p == mount || strings.HasPrefix(p, mount+"/") {
|
||||||
skip = true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !skip {
|
return false
|
||||||
paths = append(paths, p)
|
|
||||||
kinds[p] = kind
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Strings(paths)
|
|
||||||
var leaves []string
|
|
||||||
for i, p := range paths {
|
|
||||||
// A folder is followed (sorted) by the entries inside it
|
|
||||||
if i+1 < len(paths) && strings.HasPrefix(paths[i+1], p+"/") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
leaves = append(leaves, kinds[p]+" "+p)
|
|
||||||
}
|
|
||||||
return leaves
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *booter) cleanup() {
|
func (b *booter) cleanup() {
|
||||||
|
|||||||
@@ -113,15 +113,30 @@ A /home/steam/.config/example
|
|||||||
A /home/steam/.config/example/worlds
|
A /home/steam/.config/example/worlds
|
||||||
A /home/steam/.config/example/worlds/world1.db
|
A /home/steam/.config/example/worlds/world1.db
|
||||||
A /home/steam/.config/example/worlds/world1.fwl
|
A /home/steam/.config/example/worlds/world1.fwl
|
||||||
|
A /home/steam/.config/example/worlds.bak
|
||||||
A /home/steam/.config/example/Player.log
|
A /home/steam/.config/example/Player.log
|
||||||
|
C /home/steam/logs
|
||||||
|
A /home/steam/logs/server.log
|
||||||
|
C /home/steam/Steam
|
||||||
|
C /home/steam/Steam/config
|
||||||
|
C /home/steam/Steam/config/config.vdf
|
||||||
|
C /home/steam/steamcmd/linux64/steamclient.so
|
||||||
C /etc
|
C /etc
|
||||||
C /etc/hostname
|
C /etc/hostname
|
||||||
|
C /usr
|
||||||
|
C /usr/sbin
|
||||||
|
A /usr/sbin/docker-init
|
||||||
|
C /var
|
||||||
|
C /var/log
|
||||||
|
A /var/log/x
|
||||||
A /tmp/x.sock
|
A /tmp/x.sock
|
||||||
A /server/should-not-count.sav
|
A /server/should-not-count.sav
|
||||||
D /home/steam/old.txt
|
D /home/steam/old.txt
|
||||||
A /home/steam/server.pid`
|
A /home/steam/server.pid`
|
||||||
got := outsideFiles(diff, map[string]string{"/server": "v", "/data": "v"})
|
got := outsideFiles(diff, map[string]string{"/server": "v", "/data": "v"})
|
||||||
want := []string{"A /home/steam/.config/example/worlds/world1.db", "A /home/steam/.config/example/worlds/world1.fwl"}
|
// Folders whose only changes aren't the game's (logs, Docker's init) don't count either
|
||||||
|
want := []string{"A /home/steam/.config/example/worlds.bak", "A /home/steam/.config/example/worlds/world1.db",
|
||||||
|
"A /home/steam/.config/example/worlds/world1.fwl"}
|
||||||
if strings.Join(got, "|") != strings.Join(want, "|") {
|
if strings.Join(got, "|") != strings.Join(want, "|") {
|
||||||
t.Errorf("got %q\nwant %q", got, want)
|
t.Errorf("got %q\nwant %q", got, want)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user