package pzconfig import ( "encoding/json" "fmt" "os" "path/filepath" "regexp" "strconv" "strings" ) // javaLauncherFile — файл, из которого сервер берёт аргументы JVM. const javaLauncherFile = "ProjectZomboid64.json" var ( heapArgRe = regexp.MustCompile(`^-Xm[sx]`) memoryRe = regexp.MustCompile(`^([0-9]+)([kKmMgG])$`) ) // ParseMemory переводит запись вида "4g" в байты. Возвращает 0, если строка // пустая, и ошибку, если формат непонятен. func ParseMemory(value string) (int64, error) { value = strings.TrimSpace(value) if value == "" { return 0, nil } match := memoryRe.FindStringSubmatch(value) if match == nil { return 0, fmt.Errorf("объём памяти должен выглядеть как 4g или 8192m, получено %q", value) } amount, err := strconv.ParseInt(match[1], 10, 64) if err != nil { return 0, fmt.Errorf("не разобран объём памяти %q", value) } switch strings.ToLower(match[2]) { case "k": return amount * 1024, nil case "m": return amount * 1024 * 1024, nil default: return amount * 1024 * 1024 * 1024, nil } } // SetJavaMemory прописывает границы кучи JVM в ProjectZomboid64.json. Именно // оттуда start-server.sh берёт -Xmx и -Xms, поэтому менять размер памяти в // аргументах запуска бесполезно. // // maxMemory задаёт -Xmx (верхнюю границу), minMemory — -Xms. Пустой minMemory // означает, что -Xms не выставляется вовсе: JVM тогда занимает память по мере // надобности. Равные значения удобны тем, что куча не растёт на ходу, но JVM // обязана занять весь объём при старте — на машине с небольшой ОЗУ сервер // просто не поднимется. func SetJavaMemory(serverDir, maxMemory, minMemory string) error { maxMemory = strings.TrimSpace(maxMemory) minMemory = strings.TrimSpace(minMemory) if maxMemory == "" && minMemory == "" { return nil } maxBytes, err := ParseMemory(maxMemory) if err != nil { return err } minBytes, err := ParseMemory(minMemory) if err != nil { return err } // JVM с -Xms больше -Xmx не стартует вообще, поэтому ловим это здесь, а не // в логе упавшего сервера. if maxBytes > 0 && minBytes > maxBytes { return fmt.Errorf("начальный размер кучи (%s) больше максимального (%s)", minMemory, maxMemory) } 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 && heapArgRe.MatchString(str) { continue } updated = append(updated, a) } if minMemory != "" { updated = append(updated, "-Xms"+minMemory) } if maxMemory != "" { updated = append(updated, "-Xmx"+maxMemory) } 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) }