Веб-панель управления сервером Project Zomboid

Менеджер запускает сервер PZ как дочерний процесс и держит его stdin/stdout:
отсюда живая консоль без RCON, стриминг логов через SSE и корректная
остановка по цепочке quit -> SIGTERM -> SIGKILL для всей группы процессов.
Сам менеджер работает под systemd.

Что входит:
- управление жизненным циклом сервера, автозапуск и автоподъём после падения;
- метрики процесса из /proc и состояние хоста, список игроков онлайн;
- редакторы servertest.ini и SandboxVars.lua, сохраняющие исходное
  форматирование файлов;
- моды: списки Workshop ID и Mod ID с порядком загрузки, названия из Steam;
- бэкапы мира по расписанию и вручную, с ротацией и восстановлением;
- установка и обновление сервера через SteamCMD;
- вход по логину и паролю, первичная настройка по одноразовому коду.

Веб-интерфейс без сборщика, вшит в бинарник через embed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-03 22:41:11 +03:00
co-authored by Claude Opus 5
commit d0ff64c272
38 changed files with 6054 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
package pzconfig
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
)
// javaLauncherFile — файл, из которого сервер берёт аргументы JVM.
const javaLauncherFile = "ProjectZomboid64.json"
var xmxRe = regexp.MustCompile(`^-Xm[sx]`)
// SetJavaMemory прописывает лимит памяти JVM в ProjectZomboid64.json. Именно
// оттуда start-server.sh берёт -Xmx, поэтому менять размер памяти в аргументах
// запуска бесполезно.
func SetJavaMemory(serverDir, memory string) error {
memory = strings.TrimSpace(memory)
if memory == "" {
return nil
}
if !regexp.MustCompile(`^[0-9]+[kKmMgG]$`).MatchString(memory) {
return fmt.Errorf("объём памяти должен выглядеть как 4g или 8192m, получено %q", memory)
}
path := filepath.Join(serverDir, javaLauncherFile)
raw, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("прочитать %s: %w", path, err)
}
// Разбираем в map, чтобы не потерять поля, о которых мы не знаем.
var doc map[string]any
if err := json.Unmarshal(raw, &doc); err != nil {
return fmt.Errorf("разобрать %s: %w", path, err)
}
args, _ := doc["vmArgs"].([]any)
updated := make([]any, 0, len(args)+2)
for _, a := range args {
if str, ok := a.(string); ok && xmxRe.MatchString(str) {
continue
}
updated = append(updated, a)
}
// -Xms равным -Xmx: JVM сразу забирает память и не тратит время на рост
// кучи в момент, когда на сервере много игроков.
updated = append(updated, "-Xms"+memory, "-Xmx"+memory)
doc["vmArgs"] = updated
out, err := json.MarshalIndent(doc, "", " ")
if err != nil {
return err
}
if err := backupFile(path); err != nil {
return err
}
return writeAtomic(path, out, 0o644)
}