Conversation
tr | awk/sed с ранним exit обрывал pipe на Ubuntu: set -euo pipefail превращал 'tr: write error: Broken pipe' в FAIL (CI run 35892796234), хотя в Git Bash тот же код проходил. Читаем tr-вывод целиком в переменную и режем блоки из here-string, где досрочный выход приёмника безвреден.
После email/inspect валидации и onboarding unit-тестов count'ы устарели: 24 -> 25 validation-скриптов, 9 -> 14 unit-файлов, плюс строки таблиц и пояснение, что npm run typecheck проверяет и tests/type-contracts (Vitest-транспиляция регрессии типов не ловит). Убраны и устаревшие '39 тестов' / 'checkout on main'.
Первоначальное ограничение 'только ключ' пользователь отверг на ручном тесте: мастер импорта должен давать тот же выбор пароль/ключ, что и страница развёртывания. Убран main-process guard по authMethod, форма использует общий isSshAccessReady, подсказка про ключ показывается только в режиме ключа. Read-only инвариант импорта (inspect --json, без repair) не затронут; спецификация и переводы обновлены.
Пустой Dashboard: две карточки сценариев шли в две колонки; по просьбе пользователя выстроены вертикально (сверху 'Развернуть новый сервер', снизу 'Подключить существующий'). Иконки и их боксы увеличены (44->54px бокс, глиф 22->26px), избыточный mobile-override убран — сетка теперь всегда одноколоночная.
…-upgrades Корень (зафиксировано на живом VPS 31.76.240.21, Ubuntu 22.04, 2026-09-23): на свежеподнятой машине unattended-upgrades прогоняет полный security-апгрейд (~90 пакетов, 21:10:39 -> 21:20:23). dpkg вызывается ОТДЕЛЬНО на каждый пакет, между пакетами flock свободен 4-6 секунд. _apt_wait_lock на голом flock проскакивал в такой зазор, apt-get install nginx стартовал и умирал по своему lock-timeout (в Ubuntu дефолт DPkg::Lock::Timeout=120) или по внешнему kill — quickstart падал с «apt-get install nginx failed» при формально свободном lock. Фикс 11.08 (ожидание lock) помогал только против постоянно занятого lock. Что сделано: - _apt_wait_lock дополнительно считает занятым время активного рабочего процесса /usr/bin/unattended-upgrade (pgrep -f по полному пути: comm ядро обрезает до 15 символов, и воркер, и резидентный shutdown-helper выглядят одинаково «unattended-upgr» — по comm не различить, проверено экспериментом); - бюджет ожидания в quickstart 300 -> 720 секунд (покрывает наблюдённый прогон ~10 мин), текст ошибки — с явным предложением повторить позже; - явный -o DPkg::Lock::Timeout=180 (APT_LOCK_OPTS) во все apt-get install, внешний timeout install-вызовов >= 300, чтобы kill не обрывал ожидание apt; - validation/test-apt-lock-race.sh — статический контракт по всем четырём пунктам. На сервере после прогона очереди: lock свободен, dpkg -C чист, dry-run install nginx rc=0 — повторный quickstart проходит без ручных вмешательств. Не влияет: domain-режим подписки и self-steal получили те же APT_LOCK_OPTS, их логика не менялась.
Ручной тест показал: при входе в Server Settings пароль спрашивался заново (в keychain лежал только ключ, не пароль), а панель профилей открывалась под незакрытой формой доступа. Теперь SSH-пароль после ПЕРВОГО успешного входа кладётся в OS keychain (отдельный service ssh-password) и переиспользуется; в карточке — только несекретный passwordCredentialId. Отдельный sudo-пароль и passphrase приватного ключа не сохраняются. - ssh-keychain: createSshPasswordStore (save/load/remove, guard размера/CR-LF, namespace ssh-password, без plaintext-фолбека при недоступном keychain); - ssh-client: onAuthenticated стал async и дожидаемся промиса до resolve connect, чтобы keychain-запись завершилась до того, как UI посчитает вход успешным; - ssh-access: resolveStoredSshPassword (введённый вручную пароль приоритетнее сохранённого; загрузка из keychain ставит passwordPersisted=true); - ipc-handlers: persistSshPassword на onAuthenticated (сущ. сервер) и после deploy/import; ручой ввод сбрасывает флаг через setTypedSshPassword, чтобы новый пароль перезаписывал запись; servers:remove чистит keychain по countPasswordCredentialReferences; - ServerSettings: auto-connect по shouldAutoConnectServer и форма доступа сворачивается в статус «доступ подтверждён» после успеха (кнопка Изменить доступ); - plan + i18n + тесты (vault/resolver/store clear/auto-connect/typed re-persist/async await). TDD: vault, resolver, store-clear и typed-reset тесты падали до реализации. typecheck (node+web+contracts) и build зелёные; unit 80/81 (sole /bin/sh caveat).
Старые формулировки («пароли никогда не сохраняются») противоречили новой реализации. README/GUI/security на трёх языках описывают: SSH-пароль сохраняется в OS keychain после первого успешного входа и переиспользуется; отдельный sudo-пароль и passphrase не сохраняются; Server Settings подключается автоматически, а форма доступа сворачивается в статус.
Добавлена секция quickstart «apt-get install nginx failed» с корнем (per-package dpkg у unattended-upgrades и зазор flock между пакетами) и контрактами (worker check, 12-мин бюджет, DPkg::Lock::Timeout=180). Счётчик validation-скриптов 25->26, новая строка test-apt-lock-race.sh в трёх testing-доках и CLAUDE.md.
Импорт показывал только справа индекс фаз, без консоли — в отличие от
развёртывания, где видно, какие команды реально уходят на сервер. Теперь
ServerInspector пишет строки хода работ (SSH connect, \$ xrayebator
inspect --json, код, статусы компонентов, probe подписки, итог) через onLog,
IPC шлёт их как { log } на тот же канал servers:importEvent, а ImportServer
рендерит консоль под индексом фаз по образцу DeployServer.
Событие импорта стало union { step } | { log }; preload форвардит как есть.
URL подписки — bearer credential, поэтому перед выводом в консоль токен
маскируется maskSubscriptionUrl (TDD: тест падал до реализации).
«Доступ к серверу подтверждён» не объяснял, что это значит. Статус-карточка Server Settings теперь показывает фактический вход user@host:port и строку о том, где лежит секрет и что будет при следующем открытии: пароль/ключ сохранены в системном keychain (авто-вход), ключ только на этот сеанс, секрет не сохранён. Предупреждающий тон — для не-персистентных случаев. Ключи во всех трёх локалях.
Триггеры «Добавить» и переключателя языка несли «+» перед подписью; текст сидел чуть ниже центра бокса. Убрал «+». Корень смещения — базовая линия inline-SVG шеврона при наследуемой HeroUI высоте: задал обоим триггерам (.langSelect — общий класс) фиксированную высоту 34px с box-sizing и симметричным паддингом, value-спан стал flex, иконка display:block. Проверено в собранном CSS: .langSelect идёт после .button и побеждает каскад.
В меню профилей статус-бокс выглядел излишне: пользователь уже внутри сервера, а кнопка смены доступа там не нужна. Сводка (user@host:port + где лежит секрет: пароль/ключ в системном хранилище либо только на сеанс) и действие «Изменить доступ» переехали в правую часть карточки сервера в дашборде. - accessSummary() в server-access.ts (чистая функция, TDD: тест на endpoint и 4 состояния секрета — passwordSaved/keySaved/sessionOnly/notSaved); - Dashboard: правая колонка cardAccess с endpoint, подписью секрета и ghost- кнопкой «Изменить доступ»; предупреждающий тон для не-персистентных случаев; - App: view settings получил editingAccess, onEditAccess открывает настройки сразу с формой доступа; - ServerSettings: проп editingAccess отключает автоподключение, чтобы форма показалась для смены доступа; удалён осиротевший reset(); - i18n: новые dashboard.secret*/changeAccess в трёх локалях.
Состояние установки и так показывается в консоли развёртывания/импорта, а в карточке остаётся компактный чип setup status. Дублирующая 4-строчная таблица только загроможждала страницу профилей. Данные diagnostics по-прежнему сохраняются при импорте и питают чип, удалён лишь их вывод в GUI. Вместе с ней удалены осиротевшие CSS-блоки diagnosticsCard/diagnosticsGrid и неиспользуемые ключи settings.diagnostics.* + статус-ключи доступа (заменены на dashboard.secret*).
GUI-доки (EN/RU/ZH), спецификация и план описывают новую раскладку: сводка доступа и «Изменить доступ» — на карточке сервера, форма доступа в настройках только при отсутствии секрета или ошибке, таблицы «Состояние установки» в профилях больше нет. Убрано устаревшее «+ Add».
Сводка доступа выглядела как обрывистый текст поверх карточки, а «Изменить доступ» — как синяя текстовая ссылка, выбиваясь из ряда «Ключи/Настройки». Теперь это отдельная панель (фон bg-1, рамка, radius-md, заголовок «Доступ» капителью) и обычная secondary-кнопка с иконкой карандаша, как соседние кнопки карточки. Подписи про секрет сокращены до одной строки (пароль/ключ сохранён в системе, только на этот сеанс, секрет не сохранён) — раньше они переносились на две строки и распирали панель. Правка в трёх локалях. Проверено предпросмотром собранного CSS: панель центрирует endpoint, подпись и кнопку по правому краю, обе карточки не разъезжаются.
Раскладка карточки приведена к виду, который пользователь показал как эталон: заголовок с локацией под ним, ряд из трёх плиток с иконками (ОС, активные маршруты, SSH-доступ) и ряд кнопок внизу. Действие «Изменить доступ» убрано с вида — оно живёт в меню из трёх точек в плитке доступа, как на образце. - плитки ОС/маршруты/доступ: иконка + значение + подпись, все данные уже приходят из бэкенда (os и profile_count из inspect, routesCount и username из карточки), ничего не зашито под Ubuntu/один маршрут/root; - новые ключи dashboard.statOs/statRoutes в трёх локалях (иначе i18next рисовал сырые ключи — это было видно на скриншоте пользователя); - статус установки: плашка-чип заменена на обычный текст зелёным (--success, тот же цвет, что у точки онлайн), «Настроен частично» — жёлтым; - удалены осиротевшие стили прежней панели доступа и неиспользуемый импорт Chip.
GUI-доки и спецификация отражают фактический вид: заголовок с локацией, статус цветным текстом без плашки, три плитки (ОС, маршруты, SSH-доступ) и меню из трёх точек для смены доступа. Отдельно зафиксировано, что значения плиток приходят из карточки и read-only диагностики, а не зашиты под Ubuntu/один маршрут/root.
По замечаниям к скриншоту: - точка активности поднята на уровень середины флага, а не по центру всего блока; для этого шапка стала сеткой из жёлоба и контента — точка и пин стоят в жёлобе, название и локация в своей колонке; - пин места переехал в тот же жёлоб ниже, подпись локации начинается под флагом; - значки плиток увеличены с 18 до 27 px; - плитка маршрутов стала однострочной (число и подпись в ряд), но прижата влево, как содержимое соседних плиток, а не по центру. Причина, по которой прошлые правки шапки не применялись: ниже по файлу оставались дубликаты .cardTitle/.flag, перекрывавшие новые правила. Дубликаты удалены, проверено скриптом на повторяющиеся селекторы.
В мастере импорта токен уже маскировался (maskSubscriptionUrl), а лог развёртывания печатал URL подписки целиком. URL подписки — bearer credential, одного полного URL достаточно для скачивания маршрутов, поэтому обе консоли теперь показывают только начало и конец токена. Тест deployer проверяет, что полный токен в вывод не попадает.
Мастер импорта получил живую консоль с командами и статусами, но в GUI-доках она не была описана. Добавлено описание консоли в трёх языках, а в security-доках зафиксировано, что токен подписки маскируется в обеих консолях, пароли и байты ключей туда не пишутся.
Изменения после v0.5.0 (email-режим, импорт, keychain-пароль, консоль импорта, маскирование токена, apt-lock фикс) не были отражены в CHANGELOG.
Ap3x0s
added a commit
that referenced
this pull request
Sep 24, 2026
Версия 0.5.5, релизные заметки RU/EN и запись в CHANGELOG. Содержание релиза — то, что уже влито в main через PR #26 и проверено на живом VPS: импорт существующих серверов (read-only inspect), сохранение SSH-пароля в системном keychain, необязательный email при развёртывании, живая консоль импорта, маскирование токена подписки и фикс гонки apt-lock. Заметки написаны в стиле v0.5.0 — разница объясняется на языке пользователя: было «только развернуть новый сервер», стало «развернуть или подключить существующий».
Ap3x0s
added a commit
that referenced
this pull request
Sep 24, 2026
Версия 0.5.5, релизные заметки RU/EN и запись в CHANGELOG. Содержание релиза — то, что влито PR #26 и проверено на живом VPS: импорт существующих серверов (read-only inspect), сохранение SSH-пароля в системном keychain, необязательный email при развёртывании, живая консоль импорта, маскирование токена подписки и фикс гонки apt-lock.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Что внутри
Onboarding GUI: разделение двух сценариев добавления сервера, необязательный email, переиспользование SSH-доступа и доводка карточки сервера. Дизайн и план зафиксированы в
docs/superpowers/.Ветка
devсинхронизирована с актуальнойmain(dbedd4c).1. Необязательный email при развёртывании
emailModeобязателен на уровне контракта.xrayebator quickstart --without-email— тот же broad-путь, но Certbot получает--register-unsafely-without-email; фиктивный адрес не подставляется, интерактивные меню-промпты не тронуты.2. «Развернуть новый сервер» vs «Подключить существующий»
Добавитьоткрывает тот же выбор.xrayebator inspect --json(менеджер, Xray, профили, маркеры подписки, geo). Никакихquickstart/happ-setup/установки/обновления/миграций/restart/firewall — инварианты сторожит статический тест.host+port— upsert без дубликата; id карточки и host-key pin сохраняются.inspect, статусы компонентов, проверка подписки, итог).3. SSH-доступ в системном keychain
keytar(Windows Credential Manager / macOS Keychain / Secret Service) и переиспользуется после перезапуска.passwordCredentialId.electron-store, логи или командные строки.before-quitwipe), UI предупреждает; неудачная запись нового пароля очищает устаревшую ссылку.4. Карточка сервера и Server Settings
--success, что у точки онлайн), ряд плиток с иконками — ОС, активные маршруты, SSH-доступ (user@host:port+ где лежит секрет). Смена доступа — в меню из трёх точек.editingAccess=true.5. Исправления, найденные при тестировании
quickstartпадал сapt-get install nginx failedна свежем Ubuntu VPS —unattended-upgradesвызываетdpkgпо каждому пакету и держит lock ~10 минут, а голыйflock-опрос проскакивает в зазор. Теперь каждая установка ждёт активного воркераunattended-upgrade(бюджет 12 минут) и получает-o DPkg::Lock::Timeout=180; регрессия закрепленаvalidation/test-apt-lock-race.sh.tr: write error: Broken pipeна Ubuntu (pipefail+ рано выходящий читатель).…) в консоли развёртывания и в консоли импорта.Изменённые слои
xrayebatorquickstart --without-email,inspect --json, apt-lock бюджет иDPkg::Lock::Timeout, dispatch/helpsrc/main/coressh-keychain.ts(keytar + password store),ssh-access.ts(resolver),server-inspector.ts(+maskSubscriptionUrl,onLog), asynconAuthenticatedвssh-client, email-mode в deployer, password-поля store, идемпотентный upsertsrc/main/ipc-handlers.tsservers:importс шагами и логами, сохранение/очистка парольного credentialsrc/rendererImportServer(консоль),ServerSettings(автоподключение),SshAccessForm, переводы RU/EN/ZHtsconfig.contracts.json(частьnpm run typecheck), 2 новых validation-скриптаUnreleased)Проверки
bash-validationsuccess (26 validation-скриптов, Ubuntu),testsuccess, сборки Windows + macOS Intel/Apple Silicon — success.npm run typecheck(node + web + contracts) — PASS;npm run build— PASS.npm test— 83/84: единственная ошибкаshell-command.test.ts(/bin/shотсутствует на Windows, идентично baseline; Linux — источник истины).jq/uuidgenв Git Bash; Linux CI — источник истины.inspect --jsonвозвращает корректный JSON, импорт проходит, карточка заполняется реальными ОС/маршрутами/пользователем.Замечания к merge
inspect --jsonвыводит новые поля (subscription_service, опц.), парсер GUI устойчив к лишним полям.xrayebator inspect --json, поэтому на сервере нужна версия менеджера с этой командой (появилась в этой ветке; вmainеё ещё нет). На сервере со старым менеджером импорт вернёт ошибку — лечитсяxrayebator update devили обновлением из терминала. GUI сейчас показывает в этом случае общий текст про некорректный JSON; отдельное сообщение «на сервере старая версия, обновите менеджер» — возможная отдельная задача.privateKeyPathостаётся одобренным dialog'ом fallback для старых карточек; удалить можно отдельной задачей, когда ни одна карточка на него не ссылается.