fix(node): make environment progress readable from the desktop
This commit is contained in:
@@ -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"]},
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user