Профили серверов: несколько миров с раздельными настройками
Профиль — это отдельный сервер: свой мир, .ini, песочница, набор модов и лимит памяти JVM. Project Zomboid различает их по -servername, поэтому файлы профилей не пересекаются. Работает всегда один профиль: серверные файлы, игровые порты и файл запуска JVM общие, так что переключение разрешено только на остановленном сервере. - config: список профилей вместо одиночных server_name/admin_password/ java_memory; старые поля переносятся в первый профиль при загрузке; - supervisor: запускает активный профиль и выставляет его -Xmx на каждом старте, потому что файл запуска общий; - бэкапы: архив снимается с одного профиля (мир + его конфиги), имя профиля попадает в имя файла, ротация считается по профилям; - API профилей: список, создание с копированием настроек из другого профиля, правка, удаление и переключение активного; - панель: селектор профиля в шапке и вкладка со списком профилей. Удаление профиля убирает его только из панели: мир и конфиги остаются на диске, чтобы случайное нажатие не стоило сохранений. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -58,23 +58,49 @@ func (s *Server) handleListBackups(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{"backups": archives, "dir": s.backups.Dir()})
|
||||
// Фильтр по профилю необязателен: панель показывает архивы всех профилей,
|
||||
// помечая каждый, но умеет и сузить список.
|
||||
if profile := r.URL.Query().Get("profile"); profile != "" {
|
||||
filtered := archives[:0]
|
||||
for _, a := range archives {
|
||||
if a.Profile == profile {
|
||||
filtered = append(filtered, a)
|
||||
}
|
||||
}
|
||||
archives = filtered
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"backups": archives,
|
||||
"dir": s.backups.Dir(),
|
||||
"active": s.cfg.Active,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) handleCreateBackup(w http.ResponseWriter, r *http.Request) {
|
||||
var body struct {
|
||||
Note string `json:"note"`
|
||||
Note string `json:"note"`
|
||||
Profile string `json:"profile"`
|
||||
}
|
||||
if r.ContentLength > 0 && !decodeJSON(w, r, &body) {
|
||||
return
|
||||
}
|
||||
if body.Profile == "" {
|
||||
body.Profile = s.cfg.Active
|
||||
}
|
||||
if _, ok := s.cfg.Profile(body.Profile); !ok {
|
||||
writeError(w, http.StatusBadRequest, "профиль не найден")
|
||||
return
|
||||
}
|
||||
// Живой сервер держит мир в памяти, и снимок чужого профиля тут ни при
|
||||
// чём: остановка нужна только для профиля, который сейчас работает.
|
||||
profileIsRunning := body.Profile == s.cfg.Active && s.sup.State() != pzserver.StateStopped
|
||||
|
||||
name := "Резервное копирование мира"
|
||||
name := "Резервное копирование мира: " + body.Profile
|
||||
err := s.jobs.Start(name, func(ctx context.Context, log func(string)) error {
|
||||
// Опциональная остановка сервера даёт согласованный снимок: PZ
|
||||
// сбрасывает мир на диск только при сохранении.
|
||||
restart := false
|
||||
if s.cfg.Backup.StopServer && s.sup.State() != pzserver.StateStopped {
|
||||
if s.cfg.Backup.StopServer && profileIsRunning {
|
||||
log("Останавливаю сервер перед бэкапом")
|
||||
if err := s.sup.Stop(); err != nil && !errors.Is(err, pzserver.ErrNotRunning) {
|
||||
return err
|
||||
@@ -82,7 +108,7 @@ func (s *Server) handleCreateBackup(w http.ResponseWriter, r *http.Request) {
|
||||
restart = true
|
||||
}
|
||||
|
||||
_, err := s.backups.Create(ctx, log, body.Note)
|
||||
_, err := s.backups.Create(ctx, log, body.Profile, body.Note)
|
||||
|
||||
if restart {
|
||||
log("Поднимаю сервер обратно")
|
||||
@@ -144,13 +170,19 @@ func (s *Server) handleRestoreBackup(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
// Страховочную копию снимаем с того профиля, поверх которого ляжет архив;
|
||||
// у старых архивов профиль в имени не записан — берём активный.
|
||||
restoreProfile := s.backups.ProfileOf(name)
|
||||
if _, ok := s.cfg.Profile(restoreProfile); !ok {
|
||||
restoreProfile = s.cfg.Active
|
||||
}
|
||||
|
||||
taskName := "Восстановление из архива " + name
|
||||
err := s.jobs.Start(taskName, func(ctx context.Context, log func(string)) error {
|
||||
// Перед перезаписью снимаем страховочную копию текущего состояния:
|
||||
// восстановление не туда — самая дорогая ошибка в этой панели.
|
||||
log("Делаю страховочный бэкап текущего мира")
|
||||
if _, err := s.backups.Create(ctx, log, "pre-restore"); err != nil {
|
||||
if _, err := s.backups.Create(ctx, log, restoreProfile, "pre-restore"); err != nil {
|
||||
return fmt.Errorf("страховочный бэкап не создан, восстановление отменено: %w", err)
|
||||
}
|
||||
return s.backups.Restore(ctx, log, name)
|
||||
@@ -185,9 +217,10 @@ func StartBackupScheduler(ctx context.Context, s *Server) {
|
||||
s.sup.AppendLog("manager", "Плановый бэкап пропущен: выполняется другая задача")
|
||||
continue
|
||||
}
|
||||
err := s.jobs.Start("Плановое резервное копирование",
|
||||
profile := s.cfg.Active
|
||||
err := s.jobs.Start("Плановое резервное копирование: "+profile,
|
||||
func(ctx context.Context, log func(string)) error {
|
||||
_, err := s.backups.Create(ctx, log, "auto")
|
||||
_, err := s.backups.Create(ctx, log, profile, "auto")
|
||||
return err
|
||||
})
|
||||
if err != nil && !errors.Is(err, job.ErrBusy) {
|
||||
@@ -198,15 +231,17 @@ func StartBackupScheduler(ctx context.Context, s *Server) {
|
||||
}()
|
||||
}
|
||||
|
||||
// applyJavaMemory переносит лимит памяти из конфига менеджера в файл запуска
|
||||
// applyJavaMemory переносит лимит памяти активного профиля в файл запуска
|
||||
// сервера. Вызывается после установки: SteamCMD перезаписывает этот файл.
|
||||
// При обычном старте то же самое делает супервизор.
|
||||
func (s *Server) applyJavaMemory(log func(string)) error {
|
||||
if s.cfg.JavaMemory == "" {
|
||||
profile := s.cfg.ActiveProfile()
|
||||
if profile.JavaMemory == "" {
|
||||
return nil
|
||||
}
|
||||
if err := pzconfig.SetJavaMemory(s.cfg.ServerDir, s.cfg.JavaMemory); err != nil {
|
||||
if err := pzconfig.SetJavaMemory(s.cfg.ServerDir, profile.JavaMemory); err != nil {
|
||||
return err
|
||||
}
|
||||
log("Лимит памяти JVM выставлен: " + s.cfg.JavaMemory)
|
||||
log("Лимит памяти JVM выставлен: " + profile.JavaMemory)
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user