6d095d582c
1. Битая карточка прогона больше не считается отсутствующей: readRuns различает файл-нет и файл-есть-но-не-читается через readJsonOrBroken, и на нечитаемой карточке замок ЗАКРЫТ. Раньше порванный run.json молча выпадал из списка — замок открывался ровно в ту ночь, когда кончилось место. 2. samePath у замка сведён к поведению samePlanPath у печати через resolve: полный путь владельца и путь от корня хранилища от сторожа теперь один файл. Замерено: lefthook 2.1.8 подставляет staged_files путём ОТ КОРНЯ хранилища. 3. Потерянная настройка больше не читается как можно: нет disk_free_min_gb — отказ; нет hours_limit — hoursLimitHit падает громко, а checkSelfLimits гасит работника с названной причиной. 4. Дорога к московскому часу правда одна: limits берёт час у budget.moskovskoeVremya, своего Intl там больше нет. 5. Запись про полночь-как-24 переписана по замеру: этот Node отдаёт 00. Подчистку сторожит прямая проверка normalizeMskHour, а не случайная зелень. 6. Отказ по запасу без дня обновления и с бессмысленной датой от невиданного прибора теперь виден как сбой, а не как нормальный ответ: renewalDay плюс поле renews_on_problem. Каждая починка накрыта проверкой, краснеющей при её вырезании. Проверок в четырёх файлах 155, было 108. Полный набор tools: 619 зелёных в 31 файле. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
53 lines
2.9 KiB
JavaScript
53 lines
2.9 KiB
JavaScript
// Свободное место на диске. Замер — родным `statfs` Node (есть с 18-й версии),
|
||
// без вызова внешних программ: они на Windows зависят от локали и языка системы.
|
||
//
|
||
// 🪤 Ноль почти всегда сбой, а не правда о мире. Не удалось замерить — отдаём `null`,
|
||
// и `null` означает ЗАПРЕТ. «Наверное хватит» — это то, чем семь работников загоняют
|
||
// сервер в ноль за одну ночь: composer install и два npm ci на каждого.
|
||
import { statfs } from 'node:fs/promises';
|
||
|
||
export async function freeGb(path) {
|
||
try {
|
||
const s = await statfs(path);
|
||
return (s.bavail * s.bsize) / 1024 ** 3;
|
||
} catch {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
export function diskState(free, minGb) {
|
||
// 🔴 ПОРОГА НЕТ — ЭТО НЕ «МОЖНО». Раньше эта ветка отсутствовала, и потерянная настройка
|
||
// `disk_free_min_gb` снимала правило целиком, молча и зелено: `50 <= undefined` — ложь,
|
||
// значит место пропускало что угодно, вплоть до нуля свободных гигабайт. Ровно то, от чего
|
||
// лечит девиз этого файла: ноль почти всегда сбой, а не правда о мире.
|
||
// Вырежи эту ветку — покраснеет «порога не дали».
|
||
if (typeof minGb !== 'number' || !Number.isFinite(minGb)) {
|
||
return {
|
||
ok: false,
|
||
free_gb: typeof free === 'number' && Number.isFinite(free) ? free : null,
|
||
min_gb: null,
|
||
reason: 'Порог свободного места не задан (в night.config.json потеряна настройка disk_free_min_gb) — '
|
||
+ 'сравнивать не с чем. Работать без порога нельзя: «наверное хватит» и есть тот случай, '
|
||
+ 'когда сервер уходит в ноль за одну ночь.',
|
||
};
|
||
}
|
||
if (typeof free !== 'number' || !Number.isFinite(free)) {
|
||
return {
|
||
ok: false,
|
||
free_gb: null,
|
||
min_gb: minGb,
|
||
reason: 'Свободное место замерить не удалось — работать вслепую нельзя.',
|
||
};
|
||
}
|
||
// Ровно порог — уже нельзя: порог это черта, а не «можно до неё».
|
||
if (free <= minGb) {
|
||
return {
|
||
ok: false,
|
||
free_gb: free,
|
||
min_gb: minGb,
|
||
reason: `На сервере кончается место: свободно ${free.toFixed(1)} ГБ при пороге ${minGb} ГБ.`,
|
||
};
|
||
}
|
||
return { ok: true, free_gb: free, min_gb: minGb, reason: null };
|
||
}
|