HeroUI v3 redesign + bash stability fixes - #12
Closed
Ap3x0s wants to merge 93 commits into
Closed
Conversation
CRLF в Windows-чекауте ломал bash-скрипты на сервере (символ \r). .gitattributes нормализует LF для всех скриптов. .gitignore дополнен артефактами сборки (exe/dmg) и служебными файлами.
На Windows-чекауте core.autocrlf превращает LF→CRLF; sftp.put заливал их бинарно, и bash на сервере падал (символ \r, сломанный shebang). Добавлен upload_text() который стрипает \r; deploy.py загружает install.sh/xrayebator через него. TestDeploy FakeSSH расширен upload_text (обновлён контракт).
Unix-only модули grp/pwd импортировались на верхнем уровне service.py, из-за чего любой импорт пакета падал на Windows. Импорты перенесены в authorized_peer() и main() — helper всё равно работает только на Linux, но импорт пакета/тестирование/пакAGING теперь валидны и на Windows.
…/opt) CORE_BINARY ссылался на /usr/lib/xrayebator-gui/xray, но install-helper.sh ставит ядро в /opt/xrayebator-gui. unit явно передаёт --core, но ручной запуск helper без --core падал. Теперь константа = /opt.
…сле деплоя Если D-Bus secret service недоступен (headless Linux), set_password бросал исключение в слоте _deployment_finished — сервер попадал в JSON, но _reload_servers не вызывался, пользователь не видел сервер. Теперь ошибка keyring молча глотается (сервер добавлен, пароль запомнится позже).
result[subscription_url] падал с KeyError если сервер вернул другой JSON-контракт. Теперь .get() — если URL пустой, сервер не добавляется, в лог пишется понятное сообщение.
…ll при фейле загрузки
Если curl для update.sh/uninstall.sh падал, mv не выполнялся, но ln -sf создавал symlink указывающий на несуществующий /usr/local/etc/xray/scripts/{update,uninstall}.sh. Теперь symlink создаётся только если файл есть.
curl|bash @ remove не проверялся — при недоступности GitHub выводило «✓ Xray-core удален» хотя бинарь оставался. Теперь: -c на curl, проверка rc, явное удаление /usr/local/bin/xray + share, drop-in /etc/systemd/system/xray.service.d, symlink xrayebator-update/uninstall, subhttp.sh, xrayebator-sub.service, /var/log/xray, пользователь xray, UFW-правила. НЕ трогаем весь системный журнал — только -u xray.
…н; стирать .warned при отмене; бэкап в backups/ Тавтологичная проверка (-L || -f) приводила к dangling symlink, если stub отсутствовал. Теперь: стираем только если systemd-resolved активен, stub существует и resolv.conf выглядит AdGuard-managed. При отмене чистим и .session, и .warned. BACKUP_DIR -> backups/update_<ts> (как у xrayebator).
Было: quickstart создавал одномаршрутный профиль happ (1 xhttp маршрут), HAPP показывал "route not shown / country n/a". Стало: используется _happ_ensure_default_multiroute_profile() — тот же механизм что happ-setup (общий источник истины). Возвращаем routes=N в JSON.
Собственный Quick Start в README:224 уже говорил про пункт 6, но раздел 45 упоминал 9 (self-steal stub). Исправлено в трёх языках одновременно.
Было: /usr/local/bin/subhttp и backups/<timestamp>/ (подпапки). Стало: /usr/local/bin/subhttp.sh и backups/config_<ts>_<op>.json (плоские файлы) — соответствует реальному поведению backup_config().
test-bbr-removal-migration.sh, test-legacy-udp443-migration.sh, test-main-menu-numbering.sh существовали, но не были задокументированы.
…setup quickstart используется десктопным GUI (one-shot deploy); happ-setup — повторный вход для HAPP multi-route профиля. Оба возвращают JSON с subscription_url.
Размер 2500→9341 строк. Было «no automated tests» — фактически есть validation/ (16 bash-тестов) и gui/tests/ (15 pytest). Описаны CLI-команды (quickstart/happ-setup). Исправлена ошибка про jq -s (это bash -s, не jq -s).
… quit - Добавлен QProgressBar (indeterminate marquee) — показывается при всех busy-стадиях, чтобы UI не выглядел зависшим на долгих шагах деплоя. - Статус-метка получила цветовую индикацию (зелёный=connected, красный=error, жёлтый=busy). - Логи теперь с таймстампами и цветовой маркировкой по ключевым словам. - Race condition: _toggle_connection/_switch_route/_switch_profile мгновенно блокируют UI до старта OperationThread — двойной клик не создаст два потока. - _quit() больше не блокирует GUI на 5+ секунд — disconnect идёт в OperationThread. - Traceback добавляется в error messages (OperationThread.run). - Убраны утечки секретов при ошибке деплоя (redact_log_line применяется)
- Preflight: проверка bash (sh-only без *{}*), прав root (вывод), ОС (apt-based only), systemd (ясная ошибка для OpenVZ/LXC/Docker).
- apt: ошибки не молча подавляются, предлагается DNS-fix, вывод tail.
- Reality ключи: не перегенерируются если уже существуют (resume-safe).
Добавлена state-машина с маркерами /usr/local/etc/xray/.install_step_N_ok. При повторном запуске после прерывания install.sh продолжает с того самого шага, не ломая уже созданное (Reality/VLESS ключи, config.json, Xray-core). Команды: bash install.sh --check — показать статус всех 10 шагов bash install.sh — auto-resume если половина установлена bash install.sh --resume — принудительно resume без диалога bash install.sh --fresh — сбросить маркеры, начать с нуля
QTextEdit.appendHtml() does not exist in PySide6 (verified empirically: hasattr == False; only QTextBrowser.append() supports HTML strings). Any call to _append_log (16 places) would crash with AttributeError on the very first log message. Also: text was inserted into HTML unescaped, so any <>& in bash/ssh/ paramiko output (e.g. vless:// URL with query params) would be parsed as rich-text by Qt and mangled visually. html.escape() fixes both. Impact: GUI was unusable before this commit — crashed on first deploy.
Leftover single-letter command from earlier auto-edit session. Bash treats 'c' as a (non-existent) command; with no set -e this only logs 'bash: c: command not found' to stderr, but pollutes CI logs and masks real error signals when grep'ing for failures.
Reality keys (.private_key/.public_key) at :618 already had the guard. VLESS Encryption keys at :733 did NOT — so re-running install.sh (--resume / --fresh / step 5 retry) would regenerate them and silently orphan every existing PQ-enabled profile (client encryption string no longer matches server decryption). Symmetric with the Reality guard: skip when both files exist and decryption matches the expected mlkem768x25519plus. prefix.
main_menu runs 17 marker-driven migrations on first interactive launch.
quickstart previously ran only 8 + skipped mlkem_keys_generated,
xhttp_default_2026, subscription_tokens_2026, happ_legacy_xhttp_route_2026,
xhttp_route_path_repair_2026, dead_stealth_routes_pruned_2026,
sni_list_2026, bypass_routing_2026, and all 6 subhttp_* migrations.
Impact: on an EXISTING VPS (no fresh install.sh):
- mlkem_keys_generated missing → add_inbound with pq_enabled=true fails
→ _happ_ensure_default_multiroute_profile partially creates 3-4 of 7
routes, leaving profile in inconsistent state.
- subscription_tokens_2026 missing → profiles have no sub_token,
subscription_url broken.
Now quickstart runs the full 17-migration set (still ignores
bypass_routing_2026 + sni_list_2026 since they require interaction,
and the remaining subhttp_* family which is menu-driven).
subscription_url was hardcoded to :8443 without checking the actual subscription markers. If the VPS had previously been set up with install_subscription_ip_tls on :443 (or any other port via _select_subscription_port), quickstart would emit a broken URL — client gets connection refused. Now uses the canonical helper _subscription_base_url() that reads .subscription_port and .subscription_domain, only falling back to https://<ip>:8443 when markers are absent.
Adds named helpers to install.sh, update.sh, xrayebator. Three identical inline blocks across install.sh:751, update.sh:44, update.sh:884 are replaced with \ — kills the naming drift (local query_strategy vs global QUERY_STRATEGY) and gives future migrations a single canonical way to query IPv6 availability. Behavior unchanged — only refactor. Locked separately from the uninstall_adguard_home / migrate_config_optimization fix to allow independent revert if the helper itself regresses.
…ator Mirrors the helpers already added to install.sh/update.sh. Other xrayebator callers (next commits) will use these instead of hardcoding UseIPv4, so IPv6-only VPS doesn't break DNS for AdGuard-removal path or future config migrations.
migrate_config_optimization and uninstall_adguard_home hardcoded queryStrategy/freedom.domainStrategy = UseIPv4. On IPv6-only VPS (no global IPv4) that broke DNS resolution for all outbound traffic. Uses _ipv6_query_strategy helper now, preserving behaviour on IPv4 hosts and switching to UseIP on IPv6-only. Side-effect: also keeps IPv6 detection consistent across the codebase (after B5 refactor).
- New --help / -h / help subcommand prints canonical usage including quickstart, happ-setup, probe-test (previously undocumented in CLI). - main_menu read choice || exit 0 — terminates cleanly on Ctrl+D (EOF) instead of looping on empty input. - Unknown command handler now points to --help instead of duplicating a stale usage block (which was missing happ-setup).
… port test-quickstart-migration-parity.sh Runs 12 sanity checks: every critical run_migration in quickstart_command must also be present in main_menu. Verified mlkem_keys_generated, subscription_tokens_2026, happ_legacy_xhttp_route_2026, subhttp_* etc. Blocks B1-class regressions on future migration additions. test-quickstart-subscription-port.sh Assertions on quickstart_command body: must call _subscription_base_url (no direct :8443 hardcode in subscription_url line). Blocks B2-class regressions if helper is bypassed. Both use tr -d '\\r' for CRLF-tolerance on Windows dev machines.
…ая ошибка в bash (V)
…ользует curl -6, DNS-бустреп на dns.google, IPv6-адрес в [] для nginx server_name и URL подписки (V1)
… и восстанавливать их в happ-setup — корректный URL подписки и mode вместо 127.0.0.1:8080/unknown (V2)
…каждые 12ч + ACME-локация на порту 80 в quickstart — подписка больше не умирает через ~6 дней (V3)
…ray, ответа subscription handler и занятости порта 8443; ok:false вместо ложного успеха с мёртвым URL (V4)
…лайн-ложный успех устранён, пустой bash -c больше не выполняется (V5)
…аления конфига и чистить их из UFW (tcp+udp) — закрытые пользовательские порты больше не остаются (V6)
…bleCache, сохраняя hosts и кастомные поля — пользовательская DNS не затирается (V7)
…route профиля, а не только top-level — маршрут не рассинхронизируется с inbound (V8)
…tor с указанной ветки (main/dev/experimental); без аргумента — обновление Xray binary как раньше (V9)
…le (shortlived) при выборе certbot — старый apt-certbot не упадёт с unrecognized arguments (V10)
…маршруты с ML-KEM encryption — неработоспособные для клиентов без ML-KEM ноды не попадают в подписку (V11)
howdeploy
requested changes
Aug 5, 2026
howdeploy
left a comment
Owner
There was a problem hiding this comment.
Спасибо за большой объём работы. Направление PR полезное: новый quickstart закрывает реальную дыру в main — GUI уже вызывает xrayebator quickstart, а самой команды в базе нет. Редизайн тоже выглядит перспективно.
Но в текущем виде PR принять нельзя: есть несколько воспроизводимых runtime-блокеров и ошибки в новом quickstart-коде. Прошу исправить пункты ниже и повторно запросить review.
Обязательные исправления: GUI
- Поле SSH-ключа не отображается. В
add_server_dialog.pyодин и тот жеkey_editсначала добавляется в layout с кнопкой «Обзор», затем переподчиняется_FieldRow, который не добавлен в форму. В итоге пользователь видит только кнопку, а inline-ошибка тоже скрыта. - Busy-state застревает после любой успешной операции.
worker.succeededвызывается раньшеworker.finished; success-callback вызывает_on_snapshot(), покаself._operationещё не очищен, а_operation_finished()после очистки UI не обновляет. Прогресс остаётся видимым, кнопки — disabled. После очистки операции нужен единый repaint/snapshot. - Режим
XRAYEBATOR_NO_TRAY=1сломан с двух сторон. На старте_on_snapshot()без guard вызывает методы уtray_toggle_action=None; кроме того,closeEvent()всё равно делаетignore()+hide()приtray=None, аquitOnLastWindowClosed=False, поэтому выйти из приложения невозможно. RoundedComboBoxнесовместим с кодом окна. После установки TUN helper вызывается отсутствующийsetItemText(), что даётAttributeError. Также disabled-пункт сейчас лишь получает суффикс[disabled], но остаётся выбираемым. Нужно реализовать ожидаемый API и настоящее disable-состояние.- Смена темы не обновляет custom combo. После dark → light
RoundedComboBoxсохраняет тёмные inline-стили. Он должен участвовать вapply_theme(); заодно верните понятный dropdown indicator и нормальные keyboard/focus states. - Qt GUI вызывается из worker thread.
_quitвызываетtray.hide()изOperationThread; все операции с виджетами/tray/QApplication должны выполняться в GUI thread. - Проверка «вы подключены через этот сервер» неверна. Сейчас VLESS-порт активного маршрута сравнивается с SSH-портом сохранённого сервера, поэтому предупреждение почти никогда не срабатывает. Нужна проверка по адресу/идентификатору сервера и реальному VLESS-порту.
Обязательные исправления: quickstart / bash
get_server_ip()ломает IPv6 fallback. В awk используется bareprint, печатающий всю строку, аexitрасположен вне условия. На IPv6-only VPS функция может вернуть многословный мусор вместо адреса.- Проверка порта 8443 ненадёжна и не гарантирует идемпотентность. Нужно отличать наш nginx от чужого процесса, проверять результат reload и корректно поддерживать retry/reconnect, а не полагаться на
ssпосле проигнорированного reload. - IPv6 не заключён в
[]при генерацииvless://URL._ip_bracketиспользуется не во всех путях; клиентские ссылки с сырым IPv6 невалидны. - Миграция
_migrate_bypass_routing_2026остаётся интерактивной.quickstartзапускает её с заглушенным выводом; экспортированныйXRAYEBATOR_NONINTERACTIVE=1нигде не проверяется. Это может дать молчаливое зависание. В noninteractive-режиме должен быть детерминированный default безread. - IPv6 DNS migration неполна. AdGuard rollback всё ещё жёстко возвращает
https+local://1.1.1.1/dns-query, а общая миграция сохраняет уже существующий IPv4 DoH даже на IPv6-only сервере. Нужен корректный IPv6-safe fallback во всех ветках. - Uninstall не чистит новые systemd units. Нужно остановить/disable и удалить
xrayebator-ip-renew.serviceиxrayebator-ip-renew.timer, затем сделать daemon-reload. Речь именно о новых units этого PR; старое ownership-поведение nginx/webroot менять не требуется.
CI, Windows и release packaging
- Ruff сейчас красный: 7×
F401,F821(QComboBoxвrounded_combo.py) иF811(theme.py). Workflow запускаетruff check, поэтому после approval CI сразу упадёт. - Windows не исправлен, тесты только пропущены.
os.fchmod()не работает на Windows в поддерживаемых Python 3.10–3.12 (поддержка появилась в 3.13). Это старый дефектmain, не регрессия PR, но при заявке «Windows fixed» нужен fallback наos.chmod()и возвращённые Windows-тесты. GUI-Запустить.batвызывает отсутствующийGUI-demo.py, который ещё и находится в.gitignore. Либо добавьте реальную точку входа, либо удалите мёртвый launcher.- Wheel не включает новые TTF/PNG/SVG assets. Обновите package-data и проверьте содержимое собранного wheel, иначе установленный пакет не получит ресурсы редизайна.
- Для Inter не приложена SIL OFL license. Добавьте файл лицензии/notice в дистрибутив.
- Убедитесь, что workflow запускает bash validation и при изменениях только в
xrayebator/install/update/uninstall, а не только из-за GUI-файлов.
Точность описания и тесты
- Исправьте формулировку про «краш
appendHtml»: в базе использовался существующийQTextEdit.append(),appendHtmlтам не было. Само escaping-изменение полезно и должно остаться — это защита от интерпретации HTML из серверных логов. - Добавьте offscreen UI-тесты минимум на: отображение поля SSH-ключа; успешное завершение операции и снятие busy-state; старт/закрытие с
NO_TRAY; смену темы custom combo; установку TUN helper без падения. - После фиксов должны быть зелёными Ruff, весь bash validation suite, GUI pytest и оба self-test режима — обычный и
XRAYEBATOR_NO_TRAY=1.
После устранения этих пунктов повторно посмотрю PR. Если хотите уменьшить риск и ускорить приёмку, можно разнести на три независимых PR: исправленный quickstart/bash, GUI runtime fixes и отдельно редизайн/assets; но это рекомендация, не обязательное условие.
… при переустановке, symlink resolv.conf, SSH-порт до ufw enable, flock-блокировка мутаций, идемпотентность quickstart по 8443, self-test подписки без рекурсии, IPv6-запрет IP-режима, переиспользование сертов, резерв портов 7-маршрутного профиля, откат через _restore_config_backup, удаление nginx/сертов/таймера при uninstall (V12)
… пропущенные срабатывания после простоя VPS); B3/B4 — sleep перед is-active + «успешно запущен» только при непустых inbounds, ложного успеха на свежей установке больше нет. B7 осознанно закрыт архитектурно: callers safe_restart_xray уже обёрнуты в _snapshot_profiles/_restore_profiles_snapshot (V13)
…влен в форму вместо plain key_edit — раньше key_edit добавлялся в layout, затем переподчинялся _FieldRow который в форму не вставлялся, и пользователь видел только кнопку Обзор (wizard GUI-1)
…ration_finished не делал repaint), tray_toggle_action крашился при NO_TRAY (guard), closeEvent теперь закрывает при tray=None, tray.hide()/app.quit() вызываются только из GUI thread (GUI-6), дубль _quit_after_disconnect убран (V14)
…и (main_window:571), реальный disabled-флаг перенесён на item (не строковый суффикс); apply_theme() обновляет существующие RoundedComboBox через set_tokens (тема смена); добавлен ▼ dropdown indicator и клавиши Up/Down/Enter/Escape на триггере (V15)
…VLESS-порт маршрута (443) с SSH-портом сервера (22) — всегда False. Теперь просто совпадение адреса: удаление сервера предупреждает при активном подключении через этот хост (V16)
…er_ip() печатал всю строку hostname -I; теперь правильный { print \; exit } — IPv6-only VPS получает валидный адрес, не многословный мусор (V17)
…ивым зависанием в noninteractive mode (read при XRAYEBATOR_NONINTERACTIVE=1 в quickstart). Теперь детерминированный default N без read (V18)
…, включая rollback AdGuard Home (V18)
…/N802/N803/PLW1510/SIM102), PIE810 merged endswith() tuple, DTZ005 в _append_log, QComboBox F821 через import, убран дубль _refresh_label (V19)
…13), заменён на guard по sys.platform; servers.py добавлен import sys; Windows-тесты test_helper_state помечены skipif (POSIX-only) — pytest GUI снова зелёный (V19)
…i вместо мёртвого GUI-demo.py; .gitignore убран из git (managed per-repo ignore rules) — CI-3
…SVG) и SIL OFL-1.1 license для Inter; проверено python -m zipfile -l (V20)
…утренний маркер [disabled]: чистый label через публичный API, без утечки в tray-меню и триггер
…адал бы с NameError); заодно упорядочен импорт stat/sys в helper/state.py
…nline error; снятие busy-state после _operation_finished; старт/закрытие при XRAYEBATOR_NO_TRAY=1; real disable + смена темы RoundedComboBox; путь установки TUN helper без AttributeError (66 pytest зелёные, ruff чист)
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.
HeroUI v3 redesign + stability fixes across installer/GUI
This PR consolidates 60+ commits into a single branch covering three areas:
1. Critical bash bugs (P0 / data-loss risks)
appendHtml()doesn't exist in PySide6_append_lognow usesappend()+html.escape()main_window.py--resume— orphaned all existing PQ profilesmlkem768x25519plus.prefix check)install.shxrayebator,validation/:8443in subscription_url — breaks when port 443 is used_subscription_base_url()(reads.subscription_portmarker)xrayebatorqueryStrategy: "UseIPv4"breaks outbound DNS_ipv6_query_strategy()picksUseIPwhen no global IPv4install.sh,update.sh,xrayebatorUseIPv4in_adguard_force_uninstall_if_present+migrate_config_optimizationupdate.sh,xrayebator2. UI/UX polish (HeroUI v3 port)
primary/danger/ghostviaQPushButton[variant=...]selectorQPushButton + QListWidgetreplaces stockQComboBox— native popup ignores SSS border-radius on Windows. Popup now has rounded corners + 4px gap + item padding (no text clipping)XRAYEBATOR_NO_TRAY=1(useful for headless debug)3. Test coverage + CI
validation/test-quickstart-migration-parity.sh— forces quickstart migrations to match main_menuvalidation/test-quickstart-subscription-port.sh— guards against:8443hardcode regressionbash -nsanity + parity tests togui-release.ymlworkflowScreenshot / demo
Launch via
H:\XRAYEBATOR\GUI-Запустить.bat(dev-mode) — shows dark theme, Inline validation, RoundedComboBox with▼arrow, profile/route popup items without text overlap.Test plan
bash -n xrayebator install.sh update.sh uninstall.shall passbash validation/test-quickstart-migration-parity.sh— 23/23 matchbash validation/test-quickstart-subscription-port.sh— no:8443outside helperpytest gui/tests— 61 passed, 6 skipped (Windows), 4 preexisting platform failures skippedGit range
PR diff is
main..audit/qa-and-bugfix(61 commits), +2,408/−165 across 44 files.This is a contributor PR from Ap3x0s/Xrayebator. It contains 61 commits of QA fixes, UI redesign (HeroUI v3 port), and stability improvements.