fix(node): make environment progress readable from the desktop

This commit is contained in:
DCCONSTRUCTIONS
2026-09-05 19:54:08 +03:00
parent 59e14c5bc0
commit 9062126913
6 changed files with 43 additions and 4 deletions
@@ -1,6 +1,6 @@
{ {
"schema": "missioncore.node.environment/v1", "schema": "missioncore.node.environment/v1",
"revision": "ubuntu-24.04-amd64/1", "revision": "ubuntu-24.04-amd64/2",
"steps": [ "steps": [
{"id":"platform","label":"Проверка системы","description":"Операционная система и архитектура БК","requires":[]}, {"id":"platform","label":"Проверка системы","description":"Операционная система и архитектура БК","requires":[]},
{"id":"packages","label":"Установка системных пакетов","description":"OpenSSH Server и зависимости окружения","requires":["platform"]}, {"id":"packages","label":"Установка системных пакетов","description":"OpenSSH Server и зависимости окружения","requires":["platform"]},
+1 -1
View File
@@ -13,7 +13,7 @@ import tarfile
ROOT = Path(__file__).resolve().parents[1] ROOT = Path(__file__).resolve().parents[1]
VERSION = "0.4.0" VERSION = "0.4.1"
BRAND_SHA256 = "8bfee8ca9f98e0db48d98aae3af4b32493b8593e18b064a0239d513d824182af" BRAND_SHA256 = "8bfee8ca9f98e0db48d98aae3af4b32493b8593e18b064a0239d513d824182af"
@@ -38,10 +38,16 @@ def command(argv, *, timeout=15):
def trusted_directory(path, mode=0o755): def trusted_directory(path, mode=0o755):
created = not path.exists() and not path.is_symlink()
path.mkdir(mode=mode, parents=True, exist_ok=True) path.mkdir(mode=mode, parents=True, exist_ok=True)
info = path.lstat() info = path.lstat()
if not stat.S_ISDIR(info.st_mode) or info.st_uid != 0 or info.st_mode & 0o022: if not stat.S_ISDIR(info.st_mode) or info.st_uid != 0 or info.st_mode & 0o022:
raise SetupError("Каталог настройки имеет неподходящие права. Переустановите пакет Node через интерфейс системы.") raise SetupError("Каталог настройки имеет неподходящие права. Переустановите пакет Node через интерфейс системы.")
# umask 0077 must protect working files, but this nonsensitive report and
# newly created configuration directories must be traversable by readers.
# The only existing directory repaired here is our dedicated report store.
if created or path == STATE:
path.chmod(mode)
def publish(path, data): def publish(path, data):
@@ -1,6 +1,8 @@
"""Workflow acceptance boundaries without touching the host OS or credentials.""" """Workflow acceptance boundaries without touching the host OS or credentials."""
import copy import copy
import json import json
import os
from types import SimpleNamespace
from pathlib import Path from pathlib import Path
import subprocess import subprocess
import tempfile import tempfile
@@ -98,6 +100,23 @@ class EnvironmentWorkflowTests(unittest.TestCase):
self.assertTrue(target.is_symlink()) self.assertTrue(target.is_symlink())
self.assertEqual(template.read_bytes(), b'owned') self.assertEqual(template.read_bytes(), b'owned')
def test_restrictive_umask_does_not_hide_report_and_existing_report_dir_is_repaired(self):
with tempfile.TemporaryDirectory() as directory:
target = Path(directory)/'report'
with patch.object(helper, 'STATE', target), patch.object(Path, 'lstat', autospec=True, side_effect=lambda path: SimpleNamespace(st_uid=0, st_mode=os.stat(path).st_mode)):
previous = os.umask(0o077)
try:
helper.trusted_directory(target)
self.assertEqual(target.stat().st_mode & 0o777, 0o755)
target.chmod(0o700)
helper.trusted_directory(target)
self.assertEqual(target.stat().st_mode & 0o777, 0o755)
foreign = Path(directory)/'foreign'
foreign.mkdir(mode=0o700)
helper.trusted_directory(foreign)
self.assertEqual(foreign.stat().st_mode & 0o777, 0o700)
finally: os.umask(previous)
def test_delayed_service_start_retries_only_readiness(self): def test_delayed_service_start_retries_only_readiness(self):
with patch.object(helper, 'probe_node', side_effect=[OSError('not listening'), {}]) as read, patch.object(helper.time, 'sleep'), patch.object(helper, 'command') as mutation: with patch.object(helper, 'probe_node', side_effect=[OSError('not listening'), {}]) as read, patch.object(helper.time, 'sleep'), patch.object(helper, 'command') as mutation:
helper.wait_for_node() helper.wait_for_node()
+2 -2
View File
@@ -14,11 +14,11 @@ export function EnvironmentView({environment,failure,success}: {environment:Retu
return <div className="node-content"> return <div className="node-content">
<SettingsCard title="Настройка окружения" description="Установка и проверка компонентов для работы с бортовым компьютером."> <SettingsCard title="Настройка окружения" description="Установка и проверка компонентов для работы с бортовым компьютером.">
<p className="node-note">Сконфигурируйте окружение при первом запуске или повторите проверку после изменений. Приложение установит нужные пакеты, настроит службы и получит системные сведения. Вход в частную сеть и доверенный SSH-ключ подтверждаются ниже.</p> <p className="node-note">Сконфигурируйте окружение при первом запуске или повторите проверку после изменений. Приложение установит нужные пакеты, настроит службы и получит системные сведения. Вход в частную сеть и доверенный SSH-ключ подтверждаются ниже.</p>
<Button onClick={start} disabled={running||!value||!environmentSetupAvailable()}>{running?"Настраиваем…":value?.run?"Проверить и сконфигурировать":"Сконфигурировать"}</Button> <Button onClick={start} disabled={running||!value||!environmentSetupAvailable()}>{running?"Настраиваем…":"Сконфигурировать"}</Button>
{!environmentSetupAvailable()&&<p className="node-note">Настройка запускается из установленного приложения на БК.</p>} {!environmentSetupAvailable()&&<p className="node-note">Настройка запускается из установленного приложения на БК.</p>}
{pending&&(!value?.run||value.run.state!=="running")&&<ActivityIndicator label="Подтвердите действие в системном окне. Ожидаем начало настройки…" />} {pending&&(!value?.run||value.run.state!=="running")&&<ActivityIndicator label="Подтвердите действие в системном окне. Ожидаем начало настройки…" />}
{!value?loading?<ActivityIndicator label="Получаем этапы настройки" />:<p className="node-note">Сведения о настройке недоступны. Обновите страницу.</p>:<> {!value?loading?<ActivityIndicator label="Получаем этапы настройки" />:<p className="node-note">Сведения о настройке недоступны. Обновите страницу.</p>:<>
{!value.available&&<p className="node-note">Не удалось прочитать результат предыдущей настройки. Запустите настройку, чтобы заново проверить окружение.</p>} {!value.available&&<p className="node-note">Журнал предыдущего запуска недоступен. Нажмите «Сконфигурировать» — приложение восстановит доступ и выполнит этапы заново.</p>}
<ResourceList aria-label="Этапы настройки окружения">{value.profile.steps.map(specification=>{ <ResourceList aria-label="Этапы настройки окружения">{value.profile.steps.map(specification=>{
const step=value.run?.steps.find(item=>item.id===specification.id); const step=value.run?.steps.find(item=>item.id===specification.id);
const state=step?.state??"pending"; const state=step?.state??"pending";
@@ -258,3 +258,17 @@ Web API читает состояние, но не запускает приви
SSH-формы и состояния Tailscale. Чистая установка, перезагрузка и полный SSH-формы и состояния Tailscale. Чистая установка, перезагрузка и полный
GUI upgrade/remove остаются отдельными проверками P1; отладочный вызов GUI upgrade/remove остаются отдельными проверками P1; отладочный вызов
helper по SSH не заменяет ни одну из них. helper по SSH не заменяет ни одну из них.
### Исправление отчёта настройки 0.4.1
Первый запуск штатной кнопкой на физическом БК выполнил семь системных этапов:
сеть (три интерфейса), USB (13 системных объектов), SSH и Tailscale проверены.
В UI обнаружен дефект 0.4.0: umask 0077 делал каталог несекретного журнала 0700,
поэтому непривилегированные служба и окно не могли читать результат.
В профиль /2 включено явное создание каталога отчёта с правами 0755 и ремонт
его прежнего режима; файлы с ключами и чужие каталоги этим не затрагиваются.
Повторный запуск той же кнопкой выполняет ремонт. Сообщение теперь называет
точную кнопку и ожидаемое действие. Тест проверяет restrictive umask, повторный
ремонт и сохранение прав чужого каталога. Физическая GUI-проверка исправленной
версии фиксируется по наблюдаемому результату в отчёте Ops.