Директ Коммандер на Linux: Работа над ошибками

Неделю назад я выложил статью про то, как запустил Директ Коммандер на Linux. А через пару дней выяснил, что половину той инструкции можно выкинуть.
Bottles там не нужен, хватает обычного Wine из репозитория WineHQ. Заодно разобрал автообновление: Коммандер вешает красную плашку «обновление будет установлено автоматически при следующем запуске», честно качает 134 мегабайта и не устанавливает ничего. Ни тогда, ни потом, и никак об этом не сообщает.
И отдельно расскажу, как я собственным скриптом удаления снёс себе рабочую установку. Там всё сломала одна подстрока.
Коротко для тех, кто первую не читал
Директ Коммандер выходит только под Windows и macOS. Официальный установщик под Wine зацикливается на диалоге «Не удалось закрыть Direct Commander», и починить это нельзя: проверка ломается одинаково на любой сборке, включая платный CrossOver.
Обход: Коммандер это Electron-приложение, а установщик обычный NSIS-архив. Внутри лежит $PLUGINSDIR/app-32.7z с готовой программой. Распаковал, положил в префикс, запустил. Установщик по факту не нужен вообще.
Единственное жёсткое условие: стоковая сборка Wine. На игровых (soda и прочих на базе Proton) процесс стартует, проходит инициализацию и молча умирает при создании первого окна, с кодом выхода ноль. Виновата неполная реализация DirectComposition, к которой Chromium обращается, когда рисует окно.
Всё это в первой статье написано правильно, и переменную я там назвал верно. Неверным оказалось то, что я построил вокруг неё.
Лишняя переменная
В первой статье весь путь шёл через Bottles: поставь флатпак, создай бутылку, копируй файлы внутрь бутылки, запускай через /app/bin/wine, потому что это системная сборка, а не игровая.
Всё правильно, кроме Bottles: он там не нужен.
Коммандер прекрасно работает на обычном апстримном Wine 11.0 из репозитория WineHQ, в чистом префиксе, без флатпака и без всякой обёртки. Я проверил целиком: окно, авторизация через браузер, загрузка кампаний клиента с сервера, правки, отправка.
Обидно то, как я в это упёрся. Я менял по две вещи сразу: сначала настройки внутри бутылки, потом раннеры внутри неё же. Когда комбинация наконец заработала, я обрадовался и остановился. Проверил, что лишнее внутри найденного решения (флаг, патч в app.asar), но не проверил само решение целиком. Bottles был не гипотезой, а фоном, на котором шёл эксперимент, поэтому в список подозреваемых он не попал.
Работающая конфигурация это ещё не минимальная. Знал же.
Как выглядит короткий путь
Wine берётся с сайта WineHQ, а не из репозитория дистрибутива: там версии могут быть заметно старее. На Mint 22.3 кодовое имя берём от базовой Ubuntu, то есть noble, иначе индекса просто нет:
sudo dpkg --add-architecture i386
sudo mkdir -pm755 /etc/apt/keyrings
sudo wget -O /etc/apt/keyrings/winehq-archive.key https://dl.winehq.org/wine-builds/winehq.key
sudo wget -NP /etc/apt/sources.list.d/ https://dl.winehq.org/wine-builds/ubuntu/dists/noble/winehq-noble.sources
sudo apt update
sudo apt install --install-recommends winehq-stableДальше отдельный префикс и копирование распакованной программы:
export WINEPREFIX=$HOME/.wine-dc
wineboot -u # от Mono и Gecko отказываемся, Electron они не нужны
mkdir -p "$WINEPREFIX/drive_c/Program Files (x86)/DirectCommander"
cp -r ~/dc_app/. "$WINEPREFIX/drive_c/Program Files (x86)/DirectCommander/"
cd "$WINEPREFIX/drive_c/Program Files (x86)/DirectCommander"
wine "Direct Commander.exe"Всё. Ни бутылок, ни сандбокса, ни flatpak run --command=bash с портянкой внутри.
Что от этого стало лучше
Шрифты. В чистом префиксе интерфейс подхватывает системные шрифты через fontconfig напрямую, и выглядит это субъективно приятнее, чем внутри флатпака: буквы крупнее и тоньше, ближе к тому, как рисует остальная система.
Скорость. Субъективно приложение шевелится бодрее. Гипотезу имею, но проверить не смог: в сандбоксе Bottles своя сборка mesa, и отрисовка вполне могла идти программно. Проверять надо через glxinfo, а внутри флатпака его нет, ставить туда пакеты ради одного замера я не стал. Так что это именно ощущение, а не измерение.
Диалоги работы с файлами. Внутри флатпака приложение видит файловую систему от сандбокса, и добраться до своих же папок на диске бывает больно. В обычном префиксе видно все диски и весь домашний каталог как есть.
Что не изменилось: игровые сборки по-прежнему мертвы, и ровно по той же причине. Так что правильная формулировка условия звучит так: обязателен не Bottles, а стоковая сборка Wine.
Кому Bottles уже стоит и нравится, путь через него работает и остался в репозитории. Ставить его ради Коммандера смысла нет.
Красная плашка, за которой ничего нет
Теперь сюжет второй.
В первой статье я написал, что автообновление работать не будет, потому что апдейтер запускает тот самый сломанный установщик. Это было моё предположение, т.к. в моменте была самая свежая версия приложения. А потом Яндекс выпустил 3.154.2, Коммандер увидел обновление, и я наконец посмотрел, что происходит на самом деле.
Выглядит это так:
«Вы можете установить его сейчас, или оно будет автоматически установлено при следующем запуске приложения». Слово «автоматически» тут работает буквально.
Нажимать ничего не надо, программа уже всё скачала фоном, все 134 мегабайта, и положила сюда:
AppData/Local/direct-commander-updater/pending/А при закрытии она запускает новый установщик. Лог программы показывает всё как есть:
[Updater] Auto install update on quit
[Updater] Install: isSilent: true, isForceRunAfter: false
[Updater] Executing: ...\pending\Direct Commander-3.154.2-ia32.exe with args: --updated,/SРазберём что тут:
isSilent: trueи флаг/Sозначают тихий режим: установщику запрещено показывать хоть какие-то окна.isForceRunAfter: falseозначает, что после установки приложение обратно не поднимается, потому что предполагается сценарий «пользователь и так выходил, обновились и разошлись».
Дальше установщик делает то же, что делал в первой статье: проверяет, не запущено ли приложение, ошибается и хочет нарисовать диалог «Не удалось закрыть Direct Commander, нажмите Повтор». Но рисовать ему запрещено. Поэтому он молча умирает.
То есть это ровно тот же диалог из первой статьи. Только теперь невидимый.
Снаружи не происходит вообще ничего. Программа закрылась, как её и просили, установщик молча сдох, никаких окон, никаких ошибок, sayonara. Открываешь Коммандер в следующий раз, а там снова та же красная плашка и та же старая версия.
Вреда никакого: установленная копия цела, данные на месте, работать можно. Плохо другое. Впустую качаются 134 мегабайта, а человек остаётся на старой версии в полной уверенности, что обновился, потому что интерфейс пообещал сделать это сам. А Яндекс периодически поднимает минимальную версию и отключает от API тех, кто не обновился.
Попытка выключить это дело
У electron-updater есть конфиг resources/app-update.yml. Логика была простая: убрать файл, апдейтер не найдёт адрес и успокоится.
Смотрю, что внутри:
provider: generic
url: someurl
updaterCacheDirName: direct-commander-updaterurl: someurl. То есть в файле лежит заглушка, а настоящий адрес приложение подставляет в рантайме, уже своим кодом. Удалил файл: апдейтер работает как ни в чём не бывало, качает и пытается ставить. Вернул на место.
Непроверенная идея на будущее, для тех, кому эти 134 мегабайта совсем поперёк горла: положить вместо папки direct-commander-updater файл с тем же именем и снять с него право записи. Тогда закачке некуда падать. Проверю, когда выйдет следующая версия, и допишу в README.
Зато нашлась польза
Раз установщик всё равно скачан целиком и лежит в pending, качать его заново с сайта не нужно. Его можно скормить скрипту напрямую:
./install-wine.sh ~/.wine-dc/drive_c/users/"$USER"/AppData/Local/direct-commander-updater/pending/*.exeПроверил на живом обновлении: 3.153.1 обновилась до 3.154.2 распаковкой поверх, настройки, авторизация и загруженные кампании остались на месте. После этого папку pending имеет смысл почистить, иначе она копит установщики.
Как я сам себе снёс рабочую установку
А теперь самая поучительная часть, ради которой, может, и стоило всё это писать.
Раз путь через обычный Wine стал основным, репозиторий надо было переделывать: новый скрипт установки, переписанный скрипт удаления на два режима, новый README. Всё это положено тестировать, поэтому я завёл отдельную тестовую установку: свой префикс ~/.wine-dctest, своё имя лаунчера dc-test, чтобы боевую копию не трогать.
В удаляторе у меня была защита ровно от того, чтобы не снести чужую установку. Скрипт проверял, что лаунчер ссылается на тот же префикс, который сейчас удаляется:
if [ -f "$LAUNCHER" ] && ! grep -qF "$ROOT" "$LAUNCHER"; then
echo "Ярлык и установка не совпадают, ничего не удаляю."
exit 1
fiЛогика правильная. Если я перепутал имя и префикс, скрипт это увидит и остановится.
Запускаю тестовое удаление:
APP_ID=dc-test ./uninstall.sh --wineИ забываю передать WINE_PREFIX. Значит ROOT берётся по умолчанию, то есть ~/.wine-dc, боевой. А лаунчер dc-test внутри себя ссылается на ~/.wine-dctest, тестовый. Разные пути, защита должна сработать.
Она не сработала. Потому что .wine-dc это подстрока .wine-dctest.
grep честно нашёл совпадение, проверка решила, что всё в порядке, и rm -rf снёс мою рабочую установку Коммандера. Ту самую, на которой я неделю гонял клиентские кампании.
Данные уцелели: они лежат отдельно, в AppData/Roaming, и скрипт их принципиально не трогает. Переставил приложение, всё вернулось. Но ощущение было незабываемое, особенно от того, что убила меня собственная защита от ровно этой ошибки.
Чинится одним символом:
grep -qF "$ROOT/" "$LAUNCHER"Слеш на конце делает из строки границу пути. ~/.wine-dc/ внутри ~/.wine-dctest/ уже не найдётся.
И сразу же нашлась вторая такая же дыра рядом. Скрипт проверяет, не запущено ли приложение в этом префиксе, и читает переменные процесса из /proc/PID/environ:
grep -q "WINEPREFIX=$ROOT" "/proc/$pid/environ"Тот же самый баг: WINEPREFIX=/home/user/.wine-dc прекрасно находится внутри WINEPREFIX=/home/user/.wine-dctest. Плюс в environ записи разделены нулевым байтом, и обычный grep про это не знает. Правильно так:
grep -qzxF "WINEPREFIX=$ROOT" "/proc/$pid/environ"-zговорит, что разделитель нулевой байт-xтребует совпадения записи целиком-Fотключает регулярки. Теперь сравниваются записи, а не куски строк.
Мораль простая и обидная: путь это не строка, а строка с границей. Обе проверки выглядели одинаково безопасно, обе прошли ревью в моей голове, и обе были дырявые. После правки я воспроизвёл ту же команду на тестовой установке специально, чтобы убедиться, что теперь скрипт останавливается. Останавливается.
Что в итоге лежит в репозитории
install-wine.shдля обычного Wine, это теперь основной путь. Сам находит установщик в загрузках, создаёт префикс, распаковывает оба слоя, вытаскивает иконку прямо из exe черезicoutilsи делает ярлык в меню. Повторный запуск со свежим установщиком работает как обновление.install.shдля тех, у кого уже стоит Bottles.uninstall.shс режимами--wineи--bottles, тремя проверками перед удалением и внятными сообщениями, что именно пошло не так.README, перестроенный вокруг короткого пути, с разделом про обновление из
pendingи про то, какие сообщения Wine можно спокойно игнорировать.
Ссылка та же: github.com/cpa-jedi/direct-commander-linux.
Что я вынес из этой недели
Когда заработало, сократи. Первая рабочая конфигурация почти никогда не минимальная. Я проверил на лишнее содержимое решения, но не проверил само решение, и в результате неделю рассказывал людям про обязательный флатпак, которого не нужно.
Интерфейс не отчитывается о том, чего не сделал. Плашка обещает обновиться автоматически и снимается после закрытия, будто всё прошло хорошо. Никакой ошибки пользователю не показывают, и узнать правду можно только из лога, который лежит ровно там же, где лог, спасший меня в первой статье.
Защиту надо тестить. Проверка, которую ни разу не пытались провести мимо, это не проверка, а комментарий с добрыми намерениями.
Если вы повторяли первую инструкцию и у вас всё работает через Bottles, ничего переделывать не надо, оно и дальше будет работать. А если только собираетесь, начинайте сразу с обычного Wine, это три команды и минут пятнадцать.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.