Stability hardening (webui/app): - readFileLimited(): защита от OOM при чтении audit JSON (100 MB), component-status DB (10 MB) и лога задачи (50 MB) - jobs.go: буферизованный лог задачи — один открытый fd на задачу вместо open/write/close на каждую строку (устраняет тысячи syscall/сек при GPU стресс-тестах) - stability.go: экспоненциальный backoff в goRecoverLoop (2s→4s→…→60s), сброс при успешном прогоне >30s, счётчик перезапусков в slog - kill_workers.go: таймаут 5s на скан /proc, warn при срабатывании - bee-web.service: MemoryMax=3G — OOM killer защищён Build script: - build.sh: удалён блок генерации grub-pc/grub.cfg + live.cfg.in — мёртвый код с v8.25; grub-pc игнорируется live-build, а генерируемый live.cfg.in перезаписывал правильный статический файл устаревшей версией без tuning-параметров ядра и пунктов gsp-off/kms+gsp-off - build.sh: dump_memtest_debug теперь логирует grub-efi/grub.cfg вместо grub-pc/grub.cfg (было всегда "missing") GRUB: - live-theme/bee-logo.png: логотип пчелы 400×400px на чёрном фоне - live-theme/theme.txt: + image компонент по центру в верхней трети экрана; меню сдвинуто с 62% до 65% Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
68 lines
1.6 KiB
Go
68 lines
1.6 KiB
Go
package app
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
// readFileLimited reads path into memory, refusing files larger than maxBytes.
|
|
// Prevents OOM on corrupted or unexpectedly large data files.
|
|
func readFileLimited(path string, maxBytes int64) ([]byte, error) {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
data, err := io.ReadAll(io.LimitReader(f, maxBytes+1))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if int64(len(data)) > maxBytes {
|
|
return nil, fmt.Errorf("file %s too large (exceeds %d bytes)", path, maxBytes)
|
|
}
|
|
return data, nil
|
|
}
|
|
|
|
func atomicWriteFile(path string, data []byte, perm os.FileMode) error {
|
|
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
|
return fmt.Errorf("mkdir %s: %w", filepath.Dir(path), err)
|
|
}
|
|
|
|
tmpPath := path + ".tmp"
|
|
f, err := os.OpenFile(tmpPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, perm)
|
|
if err != nil {
|
|
return fmt.Errorf("open temp %s: %w", tmpPath, err)
|
|
}
|
|
|
|
success := false
|
|
defer func() {
|
|
_ = f.Close()
|
|
if !success {
|
|
_ = os.Remove(tmpPath)
|
|
}
|
|
}()
|
|
|
|
if _, err := f.Write(data); err != nil {
|
|
return fmt.Errorf("write temp %s: %w", tmpPath, err)
|
|
}
|
|
if err := f.Sync(); err != nil {
|
|
return fmt.Errorf("sync temp %s: %w", tmpPath, err)
|
|
}
|
|
if err := f.Close(); err != nil {
|
|
return fmt.Errorf("close temp %s: %w", tmpPath, err)
|
|
}
|
|
if err := os.Rename(tmpPath, path); err != nil {
|
|
return fmt.Errorf("rename %s -> %s: %w", tmpPath, path, err)
|
|
}
|
|
|
|
if dir, err := os.Open(filepath.Dir(path)); err == nil {
|
|
_ = dir.Sync()
|
|
_ = dir.Close()
|
|
}
|
|
|
|
success = true
|
|
return nil
|
|
}
|