Всем привет!
Меня зовут Артур Валиев. Недавно я уже рассказывал на Хабре о том, как мы собирали собственный RustDesk Pro при помощи патчей и кастомных сборок.
Но со временем стало понятно, что нам тесно в рамках обычной кастомизации. Мы захотели пойти намного дальше.
Начать стоит немного издалека.
Почти пять лет я проработал в районной больнице. И если кто-то когда-либо занимался поддержкой медицинских учреждений, то прекрасно знает этот зоопарк инфраструктуры: старые компьютеры, терминальные серверы, виртуальные машины, Astra Linux, закрытые сети, ограничения безопасности и постоянная необходимость помогать пользователям удалённо.
Тогда я постоянно мечтал об одном инструменте: простом удалённом клиенте, который запускался бы везде и не требовал танцев с бубном. Да и помогал мне избегать лишних выездов из теплой кровати.
Прошли годы, и теперь мы наконец сделали именно такой инструмент.
Так появился EvertyDesk Lite.
Это полностью нативный клиент удалённого доступа на Rust и egui. Один бинарник. Без браузера внутри. Без Electron, flutter. Без десятков зависимостей. Без необходимости тянуть половину интернета через репозитории.
Причём мы специально проектировали его так, чтобы он запускался даже там, где графический стек уже практически сдался. Astra Linux? РЕД ОС? Пожалуйста, старый марсианский корабль? Работает. Старая виртуалка без нормального OpenGL? Тоже запускается.
Для таких случаев мы даже реализовали программную отрисовку интерфейса (Вполне достойную к тому же), чтобы клиент можно было открыть там, где аппаратное ускорение скорее мешает, чем помогает.
Одна из неприятных проблем desktop-приложений на Linux: интерфейс может не открыться не из-за вашего кода, а из-за графического стека. Особенно это заметно в корпоративных окружениях, на старых виртуалках, Astra Linux, РЕД ОС и машинах с нестабильным OpenGL.
Обычный путь для egui-приложения выглядит так:
```rusteframe::run_native( "EvertyDesk Lite", eframe::NativeOptions::default(), Box::new(|_| Box::new(EvertyDeskApp::new())),)?;
Это хорошо, пока доступен нормальный GPU backend: wgpu, OpenGL, Vulkan, DirectX или Metal. Но если графический backend не поднялся, клиент удаленного доступа становится бесполезным: пользователь как раз и просит помощи на проблемной машине, а наш инструмент сам не запускается.
Поэтому мы сделали отдельный software UI backend. Идея простая: egui продолжает строить интерфейс, но вместо GPU мы сами tessellate’им shapes, рисуем их в CPU-буфер и показываем этот буфер через обычное окно minifb.
Упрощенно цикл:
pub fn run_software_ui() -> Result<(), String> { std::env::set_var("EVERTYDESK_EGUI_SOFTWARE", "1"); let mut window = Window::new( APP_NAME, 1100, 760, WindowOptions { resize: true, scale_mode: ScaleMode::UpperLeft, ..WindowOptions::default() }, ) .map_err(|err| format!("open CPU egui window failed: {err}"))?; window.set_target_fps(60); let ctx = egui::Context::default(); ctx.set_pixels_per_point(1.0); configure_software_fonts(&ctx); configure_style(&ctx); let mut app = EvertyDeskApp::new(); let mut painter = SoftwarePainter::default(); let mut pixels = vec![0_u32; 1100 * 760]; while window.is_open() && !window.is_key_down(MiniKey::Escape) { let (width, height) = window.get_size(); if pixels.len() != width * height { pixels.resize(width * height, 0); } let raw_input = collect_input(&window, width, height); let output = ctx.run(raw_input, |ctx| { app.update_egui(ctx); }); let primitives = ctx.tessellate(output.shapes, output.pixels_per_point); painter.apply_textures(output.textures_delta); pixels.fill(0x14181c); painter.paint(&mut pixels, width, height, &primitives); window .update_with_buffer(&pixels, width, height) .map_err(|err| format!("CPU egui window update failed: {err}"))?; } app.shutdown(); Ok(())}
Фактически здесь есть четыре шага:
-
Собираем ввод из minifb: мышь, клавиатура, размер окна.
-
Передаем его в egui::Context как RawInput.
-
Получаем от egui набор shapes и tessellate’им их в paint primitives.
-
Рисуем primitives в обычный Vec<u32> и отправляем буфер в окно.
Самое важное: бизнес-логика интерфейса не дублируется. Основное приложение по-прежнему обновляется через один метод:
app.update_egui(ctx);
То есть обычный GPU-режим и software-режим используют один и тот же UI-код. Разница только в том, кто в конце рисует пиксели.
Отдельная проблема была с кириллицей. В software-режиме нельзя рассчитывать, что система сама красиво подхватит нужный шрифт. Поэтому мы явно ищем установленный шрифт с кириллицей:
fn load_cyrillic_font() -> Option<(String, Vec<u8>)> { const FONT_PATHS: &[(&str, &str)] = &[ ("Noto Sans", "/usr/share/fonts/truetype/noto/NotoSans-Regular.ttf"), ("DejaVu Sans", "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"), ("Liberation Sans", "/usr/share/fonts/liberation-fonts/LiberationSans-Regular.ttf"), ("Segoe UI", "C:\\Windows\\Fonts\\segoeui.ttf"), ("Arial", "C:\\Windows\\Fonts\\arial.ttf"), ]; for (name, path) in FONT_PATHS { if let Ok(data) = std::fs::read(path) { if !data.is_empty() { return Some((format!("system-{name}"), data)); } } } None}
И подключаем его в egui:
fn configure_software_fonts(ctx: &egui::Context) { let mut fonts = FontDefinitions::default(); if let Some((name, data)) = load_cyrillic_font() { fonts.font_data.insert(name.clone(), FontData::from_owned(data)); for family in [FontFamily::Proportional, FontFamily::Monospace] { fonts.families.entry(family).or_default().insert(0, name.clone()); } } ctx.set_fonts(fonts);}
Запускается это так:
EVERTYDESK_RENDERER=software ./evertydesk-lite
В итоге получилось два режима:
-
обычный режим через eframe, wgpu и glow;
-
аварийный software-режим через egui + minifb + CPU buffer.
Это не замена нормальному GPU-rendering. Это запасной вход в приложение, когда графика на машине уже сломана или слишком старая. Для удаленного доступа это критично: если клиент нужен именно для диагностики проблемной системы, он не должен сам падать на первом же проблемном драйвере.
И сразу хочу обозначить важный момент.
Этот материал не про продвижение очередного коммерческого продукта. Код открыт, позвольте поделится опытом разработки, и будьте добры дочитайте статью до технического «Giardino della Bellezza«
Наоборот — весь проект изначально задумывался как открытая платформа для экспериментов. Исходный код доступен, любой желающий может собрать клиент под собственные задачи, заменить серверную часть, встроить его в существующую инфраструктуру или использовать отдельные идеи в своих проектах.
Для меня гораздо интереснее рассказать не о результате, а о пути, который к нему привёл.
Потому что в какой-то момент стало очевидно: удалённый доступ давно перестал быть задачей про передачу изображения с одного компьютера на другой.
Исторически такие системы решали довольно простую проблему — дать возможность увидеть удалённый экран и получить контроль над мышью и клавиатурой. Этого было достаточно, пока компьютеров было немного, инфраструктуры были относительно простыми, а большинство операций выполнялось вручную.
Сегодня ситуация выглядит иначе.
Современный администратор работает не с одним компьютером, а с сотнями и тысячами устройств. Он управляет сервисами, политиками, обновлениями, безопасностью, автоматизацией и мониторингом. Его основным инструментом всё чаще становится не графический интерфейс, а терминал.
По сути, речь идёт не об искусственном интеллекте в привычном маркетинговом смысле, а о новом интерфейсе взаимодействия с инфраструктурой.
И чем дальше развивался проект, тем отчётливее становилось ощущение, что мы строим уже не просто клиент удалённого доступа.
Мы постепенно собираем рабочее место инженера поддержки — инструмент, в котором удалённый экран, терминал, автоматизация и ИИ становятся частями одной системы.
Когда «нативный интерфейс» внезапно перестаёт быть нативным
Практически любой Rust GUI-проект начинается одинаково:
fn main() { eframe::run_native( "EvertyDesk Lite", eframe::NativeOptions::default(), Box::new(|_| Box::new(EvertyDeskApp::new())), ) .unwrap(); }
На рабочем ноутбуке разработчика всё выглядит отлично.
Проблемы начинаются позже.
Оказывается, что часть Linux-машин использует старые версии Mesa. Где-то нестабильно работает OpenGL. Где-то отсутствует Wayland. Где-то система находится в закрытом контуре и пользователь физически не может установить дополнительные зависимости.
В таких условиях фраза «обновите драйвер» означает только одно — пользователь остаётся без помощи.
Поэтому в EvertyDesk Lite появился отдельный режим программного рендеринга интерфейса.
В обычном режиме используется аппаратное ускорение через стандартный стек egui. Если графическая подсистема начинает вести себя непредсказуемо, приложение может перейти на программный backend, который рисует интерфейс силами процессора.
Да, такой режим работает медленнее.
Но он работает.
И для удалённого доступа это намного важнее.
EVERTYDESK_RENDERER=software ./evertydesk-lite
Если система способна открыть окно X11, шанс запустить клиент остаётся даже на крайне проблемном оборудовании.
Кириллица — это тоже инфраструктура
Пока приложение работает на стандартном рендерере, вопрос шрифтов кажется незначительным.
Но как только появляется fallback-интерфейс, выясняется, что поддержка текста становится отдельной инженерной задачей.
Поддержка работает не только с английским языком.
В интерфейсе встречаются:
-
комментарии операторов;
-
заметки в адресной книге;
-
вывод терминала;
-
сообщения об ошибках;
-
диагностические данные.
Поэтому приложение принудительно настраивает UTF-8 окружение и пытается подобрать системный шрифт с поддержкой кириллицы.
fn configure_locale_for_text_input() { // Настройка UTF-8 locale}fn load_cyrillic_font() -> Option<(String, Vec<u8>)> { // Поиск доступного системного шрифта}
На первый взгляд это выглядит как мелочь.
На практике именно такие детали определяют, сможет ли оператор пользоваться системой на рабочем месте.
Кодеки должны деградировать красиво
Одна из самых распространённых ошибок при разработке удалённого доступа — зависимость от одного идеального медиаконвейера.
В реальной эксплуатации идеального окружения не существует.
Разные операционные системы предоставляют разные возможности:
-
аппаратное декодирование;
-
программное декодирование;
-
различные версии мультимедийных библиотек;
-
различные наборы кодеков.
Поэтому клиент должен уметь работать в нескольких режимах.
Например:
-
потоковое видео;
-
облегчённый режим;
-
резервная передача кадров изображениями.
Внутри клиента это выглядит как выбор доступного режима работы:
pub enum LiveVideoMode { ScreenshotOnly, H264, H264Vpx, }
Если современный видеопайплайн недоступен, приложение не должно прекращать работу.
Оно должно продолжать показывать удалённый рабочий стол менее эффективно, но всё ещё полезно.
Для поддержки это критически важно.
Почему хорошие сообщения об ошибках важнее красивых кнопок
Подключение к удалённым сервисам редко ломается красиво.
Неверный пароль, просроченная авторизация, ошибка конфигурации, проблемы сети — всё это выглядит одинаково, если приложение показывает пользователю только фразу:
Ошибка подключения.
Для инженера поддержки такое сообщение бесполезно.
Поэтому в EvertyDesk Lite особое внимание уделяется диагностике.
Вместо абстрактных уведомлений система старается показывать максимально конкретную причину сбоя:
Ошибка авторизацииКод ответа: 401Описание: доступ запрещён
или
Не удалось установить соединениеПричина: удалённый узел недоступен
Такой подход значительно сокращает время поиска проблемы.
Подключение без пароля требует подтверждения
Удалённый доступ — это не только удобство, но и безопасность.
Если оператор подключается без заранее известного пароля, удалённая сторона должна явно подтвердить входящий запрос.
Поэтому система рассматривает запрос на подключение как отдельное событие.
pub enum HostEvent { ApprovalRequested, ClientConnected, ClientDisconnected, }
В интерфейсе появляется окно подтверждения, позволяющее принять или отклонить подключение.
Такой механизм выглядит очевидным, но именно он отделяет инструмент поддержки от потенциального инцидента безопасности.
Терминал часто важнее рабочего стола
Удалённый рабочий стол удобен для понимания контекста.
Но большая часть административной работы выполняется через консоль.
Через несколько минут после подключения оператор обычно начинает выполнять команды:
systemctl status journalctl ip a df -h
Передавать всё это через эмуляцию клавиатуры неудобно.
Поэтому терминал рассматривается как отдельный инструмент, а не как дополнение к удалённому экрану.
Для него используются собственные каналы передачи данных, собственные буферы и отдельный интерфейс отображения.
pub enum ShellMessage { Open, Input, Output, Close, }
Это позволяет работать значительно быстрее и надёжнее.
Искусственный интеллект не должен управлять сервером
«Зачем вообще ИИ в системе удалённого доступа?
На самом деле не для генерации текста и не для модных демонстраций.
Проблема возникает тогда, когда администратор сталкивается с огромным объёмом технической информации. Логи, вывод команд, ошибки служб, сетевые трассировки, журналы событий — всё это нужно быстро анализировать и связывать между собой.
Например, пользователь жалуется, что не запускается приложение. Администратор подключается к машине, открывает терминал, проверяет состояние службы, просматривает журнал событий и получает несколько десятков строк ошибок. Формально вся информация уже есть, но её ещё нужно интерпретировать.
ИИ может сразу объяснить: служба не стартует из-за отсутствия сертификата, сертификат просрочен, а связанная ошибка уже присутствует в системном журнале. Вместо поиска по документации и форумам инженер получает готовую гипотезу за несколько секунд.
Другой пример — массовое администрирование. После обновления на сотне серверов часть узлов начинает возвращать ошибки. ИИ способен проанализировать результаты выполнения команд на всех узлах одновременно, сгруппировать одинаковые проблемы и показать, что 87 серверов работают штатно, а на 13 отсутствует необходимая библиотека или не применился пакет обновления.
То есть ИИ здесь работает не как оператор инфраструктуры, а как интеллектуальный анализатор данных. Он не принимает решения и не выполняет действия самостоятельно. Он помогает человеку быстрее понять, что именно происходит в системе и куда смотреть дальше.»
В последние годы стало популярным добавлять AI практически в любой продукт.
В администрировании такой подход может быть опасен.
Автоматическое выполнение команд на удалённой системе создаёт слишком высокий риск ошибок.
Поэтому в EvertyDesk Lite искусственный интеллект рассматривается исключительно как помощник.
Он получает ограниченный контекст:
-
задачу оператора;
-
часть истории терминала;
-
последние сообщения системы.
После чего предлагает:
-
вероятную причину проблемы;
-
возможную команду;
-
рекомендации по дальнейшей диагностике.
Например:
Диагноз:Служба не запущена.Рекомендуемая команда:systemctl restart service-nameПосле выполнения проверьте:systemctl status service-name
Ключевой принцип остаётся неизменным:
AI может советовать.
Решение принимает человек.
Режим службы — отдельный продукт внутри продукта
Запустить клиент вручную несложно.
Гораздо сложнее обеспечить доступ к системе ещё до входа пользователя.
Для этого необходим отдельный режим работы службы.
На Linux это обычно systemd unit:
[Unit]Description=EvertyDesk Lite Service[Service]Type=simpleExecStart=/usr/local/bin/evertydesk-lite --hostRestart=always[Install]WantedBy=multi-user.target
На Windows используется аналогичный сервисный режим.
С инженерной точки зрения это совершенно другой сценарий эксплуатации со своими ограничениями, требованиями безопасности и особенностями взаимодействия с графической подсистемой.
Сборка не должна зависеть от памяти разработчика
Любой Linux-проект рано или поздно сталкивается с одной и той же проблемой.
Разработчик пишет:
У меня всё собирается.
Пользователь отвечает:
У меня нет.
Обычно причина скрывается в забытых пакетах и зависимостях.
Поэтому проект должен содержать собственные инструменты диагностики окружения.
Например:
echo "OS:"cat /etc/os-releaseecho "Graphics:"glxinfo -Becho "Rust:"rustc --versioncargo --version
Такие проверки позволяют быстро понять состояние системы и избежать долгой переписки.
Что получилось в итоге
Сегодня EvertyDesk Lite представляет собой компактный клиент удалённого доступа, ориентированный не на демонстрационные сценарии, а на реальные задачи поддержки и администрирования.
Проект включает:
-
подключение к удалённым устройствам;
-
подтверждение входящих подключений;
-
адресную книгу;
-
историю сеансов;
-
встроенный терминал;
-
AI-помощника для диагностики;
-
поддержку различных режимов передачи изображения;
-
резервные сценарии работы;
-
программный рендеринг интерфейса для проблемных систем;
-
режим службы для постоянного доступа.
Главный вывод, который появился во время разработки, оказался довольно простым.
Удалённый доступ нужен не тогда, когда всё работает идеально.
Он нужен именно тогда, когда система уже находится в нестандартном состоянии. (Да да банальщина =))
Поэтому хороший клиент удалённого доступа должен быть готов работать на несовершенной машине, в несовершенной сети и в несовершенном окружении.
Без лишнего пафоса: Наш прокачанный клиент и его режим скриптов — это просто мёд. Принтер на Astra Linux залетел в одну команду. Общая библиотека автоматизации явно спасет сисадминов от кучи занудной работы.
Именно под такие условия и проектируется EvertyDesk Lite.
Спасибо, что дочитали до конца! Понимаю, что успел рассказать далеко не обо всех нюансах. Обязательно пишите комментарии и задавайте свои вопросы — с радостью всё обсудим.
Всем отличной и продуктивной недели!
Личные контакты для связи:
Email: info@everty.ru
https://github.com/vaalimusic/deskeverty-lite-4-all
https://desk.everty.ru/
ссылка на оригинал статьи https://habr.com/ru/articles/1042428/