Веб-панель управления сервером 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
+45
View File
@@ -0,0 +1,45 @@
package config
import (
"fmt"
"time"
"gopkg.in/yaml.v3"
)
// Duration — time.Duration, который в YAML выглядит как "90s" или "6h", а не
// как число наносекунд: конфиг правят руками.
type Duration time.Duration
// Duration возвращает значение в виде time.Duration.
func (d Duration) Duration() time.Duration { return time.Duration(d) }
// Seconds возвращает длительность в секундах — удобно для JSON-ответов API.
func (d Duration) Seconds() int64 { return int64(time.Duration(d).Seconds()) }
// String печатает длительность в том же виде, в каком она пишется в конфиге.
func (d Duration) String() string { return time.Duration(d).String() }
// MarshalYAML пишет длительность строкой.
func (d Duration) MarshalYAML() (any, error) { return time.Duration(d).String(), nil }
// UnmarshalYAML принимает и строку ("90s"), и число — число трактуется как
// секунды, потому что так его естественнее всего написать руками.
func (d *Duration) UnmarshalYAML(node *yaml.Node) error {
var asString string
if err := node.Decode(&asString); err == nil {
parsed, err := time.ParseDuration(asString)
if err != nil {
return fmt.Errorf("не разобрана длительность %q: %w", asString, err)
}
*d = Duration(parsed)
return nil
}
var asSeconds int64
if err := node.Decode(&asSeconds); err != nil {
return fmt.Errorf("длительность должна быть строкой вида 90s или числом секунд")
}
*d = Duration(time.Duration(asSeconds) * time.Second)
return nil
}