Приложение ReadySet (OEM с первоначальной настройкой системы) необходимо где-то запускать. На данный момент это делает Cage, оконный менеджер (WM) рассчитанный для киосков. Однако он имеет массу проблем: - Программная отрисовка приводит к спаму в логах, очень медленной производительности и полному зависанию - По каким-то причинам на некотором поддерживаемом оборудовании он также не может задействовать DRM, что приводит к проблемам выше - Приложение некорректно отображается на нескольких мониторах - Невозможно сменить раскладку Нам необходимо подобрать оконный менеджер Wayland, который: - Развивается, получает исправления - Нормально дружит с NVIDIA и другими картами - Не боится программной отрисовки - Может запускаться от root (опционально) - Не привязан к рабочему окружению и лёгкий Что рассмотрели: - Cage был рассмотрен ранее - X11 даже не рассматриваем, мы отказываемся от него и имеет много зависимостей - Mutter привязан к GNOME и потащит много зависимостей - Hyprland имеет проблемы с программной отрисовкой и NVIDIA - Sway, вероятно, не дружит с NVIDIA. Разработчик выступает против неё Сейчас рассматриваю KWin. Пакет kwin из репозитория Sisyphus имеет очень много зависимостей и у меня есть предположения, что многие из них ему не нужны вовсе. Это мне предстоит выяснить. Если удастся минимизировать количество зависимостей, то kwin станет отличным выбором, поскольку: - Его стабильно развивает KDE, одно из самых больших сообществ - В целом довольно прост в запуске, не требует никаких конфигов (только для раскладок) - Производительный, работает много где, в том числе и с программной отрисовкой Часть оконных менеджеров (в том числе kwin) открывают окно не на весь экран, поэтому необходимо зарепортить в ReadySet поддержку опции для полноэкранного запуска.
Оконные менеджеры и сам GTK очень любят создавать разные конфиг-файлы, поэтому запускать оконный менеджер будем от другого пользователя. ReadySet уже взаимодействует с Polkit и в будущем будем придерживаться того же. Для удобного создания пользователя существует и features.in/deflogin в mkimage-profiles. Столкнулся также с багом в KWin, который я зарепорчу позже. По умолчанию KWin рендерит курсор только из темы Breeze. И если тема отсутствует, то и курсор не рендерится. В качестве обходного пути можно либо подать конфиг-файл с другой темой курсора. либо кидать breeze курсоры в /usr/share/icons. Всё остальное уже по рутине дня: Передавал репозиторий хешера по ftp из билдера в виртуальную машину для тестирования KWin, научился устанавливать rpm пакеты с игнорированием зависимостей (--nodeps, ради тестов исключительно). Узнал, что при помощи `epm policy kwin` можно увидеть все источники. Узнал о существовании epmqf для определения к какому пакету принадлежит директория. Много экспериментировал со spec-файлами и пересборкой, но пока не могу придти к чему-то. Зависимости всё равно остаются. даже не уменьшаются. AutoReq: no также не помогает. Я что-то упускаю, так что если завтра буду на одной и той же точке. то попробую изучить hasher, spec файлы и саму команду для сборки.
KWin имеет очень большое количество зависимостей и является практически самым тяжёлым оконным менеджером. В другом баге я ещё буду анализировать зависимости, которые возможно от него отвязать, но это всё равно маленькое число. Будем смотреть в сторону Mutter (оказалось, у него не так много зависимостей) или Weston. Пока начал тестировать первое, заметил, что просто установить mutter недостаточно и для запуска требует какие-то ещё дополнительные зависимости. Буду разбираться. apt-get сообщает, что в распакованном виде (со всеми зависимостями, поэтому пока это оценочные данные): - Mutter - 834 мб - KWin - 1000 мб - Weston - 530 мб Из другой рутины дня: - Изучил как работать со spec-файлами, менять релиз и что нужно делать коммиты (gammit -a) - Взял конфигурацию для ~/.config/eterbuild, чтобы отключить тот же sisyphus_check - Тестировал работу KWin с разными зависимостями - Зарепортил баг в ReadySet связанный с полноэкранным запуском: - https://altlinux.space/alt-gnome/ReadySet/issues/37
Удалось установить Mutter, запустить его. Для этого потребовался mutter, mutter-gnome (без него выдаёт какую-то ошибку с GNOME схемами) и xkeyboard-config. Спасибо Кириллу за помощь с последней зависимостью, без него я бы долго искал в чём дело. Эта зависимость также нужна KWin для нормальной работы, начинаю задумываться, что её необходимо сделать обязательной для данных пакетов. Оконный менеджер запускается, работает, но есть очень сильные проблемы с тормозами. Буду разбираться. вероятно, не хватает библиотеки libwayland-egl (KWin без неё не запускается). Удалось собрать новую версию KWin с минимальными зависимостями. С 1000 мб потребление уменьшилось до 550 мб, что практически на уровне с Weston! Для этого изучил CMakeLists.txt и отключил все дополнительные функции, что не нужны для работы с ReadySet. Нечто подобное нужно будет просмотреть и для Mutter. Но стоит понимать, что это всё пока оценочные данные! Это значит, что необходимо более тщательно проверить работу оконных менеджеров, лёгкость удаления остаточных зависимостей, а также учесть общие зависимости ReadySet и оконных менеджеров. Чем я в итоге и займусь. Из рутины дня: - Узнал, как запускать приложения на разных оконных менеджерах - Записал ещё несколько багов с KDE, попытаюсь воспроизвести и зарепортить - Записал в заметки также зарепортить про xkeyboard-config - Узнал, что иногда файлы хешера не удаляются, но удалить их можно через «hsh-rmchroot $TMP/hasher-sisyphus-64/». Правда Виталий Липатов сказал, что удалять файлы хешера не нужно, поэтому в данный момент я прислушаюсь к этому совету и не стану так делать без особой нужды. - Узнал, что по умолчанию репозиторием хешера на нашей сборочнице является Sisyphus - В планах расследовать, по каким причинам зависают виртуалки в Proxmox - Жду, когда Кирилл закончит новую структуру ximper.mk :)
С оконным менеджером определился, это будет Mutter, так как он имеет больше всего общих зависимостей с ReadySet и на фоне других будет меньше всего загружать пакетов. Изучил причину, по которой ReadySet не запускался в других окружениях. Оказалось из-за отсутствия зависимости gnome-control-center-data Поэкспериментировал со сборкой минимальной версии Mutter, но эти усилия не стоят того, ибо сам по себе он уже довольно маленький. Сейчас поставил себе задачу придумать способ автозапуска Mutter с ReadySet, желательно не от root. В отличии от Cage, в этом плане KWin/Mutter более «капризны» и требуют logind. В идеале хотелось бы сделать это с DynamicUser от systemd, с которым немного я и разбирался сегодня: - https://0pointer.net/blog/dynamic-users-with-systemd.html - https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html
Я потратил довольно много времени на эксперименты с запуском Mutter и понял, что довольно много вещей в systemd и некоторых модулях просто не понимаю и я решил, что займусь изучением systemd. Я использовал сразу несколько источников, чтобы вкратце узнать и историю systemd и его использование, работу и так далее: - systemd для администраторов» от Lennart Poettering с русским переводом и уточнениями от Сергей Пташник (она очень маленькая, можно на одном дыхании прочитать) - https://wiki.archlinux.org/title/Systemd - https://habr.com/ru/companies/timeweb/articles/824146/ - https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html - https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html - https://www.freedesktop.org/software/systemd/man/258/systemd.environment-generator.html - https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html - https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html В свободное время я также прочёл и анонс systemd, он был очень интересным: - https://tux-the-penguin.blogspot.com/2010/09/systemd.html - https://tux-the-penguin.blogspot.com/2010/09/systemd-ii.html Из того, что я изучил, это: - Синтаксис systemctl, как он запускает, останавливает процессы, взаимодействие с системой, службами и прочее - Узнал про systemd-шаблоны - Узнал, как systemd создаёт символические ссылки в своих директориях и как через это определяется порядок загрузки (есть специальные .d директории, например для WantsBy это .wants директория) - Узнал как высчитываются неявные зависимости (например, если Type=dbus, то автоматически ставится After=dbus. Тоже самое и с дисковыми устройствами и не только) - Узнал, как systemd держит под опекой процессы и не позволяет им сбежать. Делается это через CGROUP - Узнал про секцию [Unit], в том числе: - Про разновидности зависимостей (Wants, Requires, Requisite, BindsTo, PartOf, Upholds). У всех них разное поведение, например, у BindsTo жёсткая привязка и в случае остановки одного юнита он также остановит и другие юниты. - Использование Conflicts, список юнитов, которые не могут быть запущены вместе с нашим юнитом - Другие параметры вроде PropagatesReloadTo/PropagatesStopTo/Also, которые распространяют операции reload/stop/enable|disable на другие юниты - Огромное количество различных возможных проверок, которые позволяют запускать юнит только в определённой среде, например, при определённой версии ядра, при наличии аккумулятора, определённой архитектуры, при работе от VM и так далее - systemctl edit или как делать дроп-ин файлы со своими изменениями. Способа сделать изменение в systemd файле два и один из них это либо дроп-ин, либо полное копирование. У каждого свои плюсы и минусы. - Узнал про Specifier, специальные переменные, которые можно подставлять - Узнал про секцию [Service], в том числе: - Разные Type и как они работают. Например, Type=forking подходит для процессов, которые демонизируют себя после запуска. Или Type=idle, который запустится позже всех остальных. - Узнал, что есть возможность разными способами доложить systemd о том, что процесс был запущен. И для этого есть возможность настройки специальных таймаутов, которые не будут заставлять систему долго ждать запуска сервиса. - Много параметров, связанные с Exec. В том числе, ExecStart, ExecStop, ExecReload, а также их Pre/Post команды - Узнал про Restart= и как его можно настроить. Можно, например, не заставлять службу перезапускаться, если она завершила работу без ошибки - Можно устанавливать рабочую директорию, корневой образ и прочее - Кроме этого есть ещё большое количество параметров, позволяющие запускать службу от другого пользователя или сделать свою песочницу (ограничить директории, сокеты, определённое поведение, вызовы, память и оочень многое другое) Сейчас я практикую и уточняю некоторые нюансы, поэтому постараюсь в следующем отчёте уточнить некоторые моменты и дописать. Но в целом уже чувствую себя достаточно уверенным пользователем systemd, поэтому не боюсь начать работу с ним.
Удалось запустить Mutter через systemd, нашёл некоторые недостающие пакеты KDE для целостной системы (но это тема для другой баги). Параллельно изучал systemd. К тому же пришлось создать свою базу знаний, ибо искать по мануалам оказалось очень муторным и в некоторых местах не хватало русского перевода для понимания. Но из того, что успел сегодня закрепить и изучить: - Каталоги, по которым systemd ищет юниты, сервисы и прочее (сервисные и пользовательские) - Они могут отличаться: systemd-analyze --user unit-paths - Синтаксис и его спецификаторы - Как работают алиасы (например, default.target обычно имеет алиас multi-user.target или graphical.target). Алиасы не могут использоваться с командой preset. - Как лучше создавать свои Drop-in конфигурации - Как работает экранирование строк в именах юнитов (например, символ «/» заменяется на «-») - На практике с Mutter лучше выявил работу неявных зависимостей и зависимостей по умолчанию (можно также регулировать через DefaultDependencies=) - Как работает сборка мусора у systemd (регулируется ещё через CollectMode=) - Попрактиковал установку зависимостей и увидел на практике, как systemd создаёт символические ссылки в .wants директориях и прочих - RequiresMountsFor= (или WantsMountsFor=, отличаются поведением) для зависимостей от mount-юнитов - Через StopWhenUnneeded=True можно указать останавливать юнит, если он никому не нужен - Через RefuseManualStart=, RefuseManualStop= можно ограничить пользователя произвольно запускать службу (например, она должна запускаться от другой службы) - Через FailureAction=, SuccessAction= можно настроить действия, которые будут происходиьт при ошибке или успешной загрузке службы. Например, можно отправить в перезагрузку систему - Попрактиковал настройку таймаутов через JobTimeoutSec=, JobRunningTimeoutSec=, можно также настраивать какие коды выхода будут приниматься для состояния Failed и Success через FailureActionExitStatus=, SuccessActionExitStatus= - Можно настроить частоту запуска юнита через StartLimitIntervalSec=interval, StartLimitBurst=burst - Попрактиковал Conditions (условия) и Asserts (требования). Первый просто не запускает службу в случае, если условия не подходят. Вторые переводят службу в faile.d - Изучил секцию [Install]. Она влияет только на включение службы, но не её работу. Можно управлять зависимостью службы, например при enable сувать службу как WantedBy другой службы. Довольно частая практика, как я заметил
Изучил (на практике с Mutter тоже) один из самых больших мануалов — systemd.exec: это опции, общие для сервисов, сокетов, точек монтирования и swap-устройств. Узнал больше о работе неявных зависимостей — например, LogNamespace= автоматически создаёт зависимость от socket-юнитов вроде systemd-journald@.service. Узнал про множество переменных, устанавливающих пути. Среди них: - ExecSearchPath= — переопределяет или дополняет $PATH, который используется при разрешении команд в ExecStart=. - WorkingDirectory= — рабочий каталог, в котором будет запущен сервис. - RootDirectory= — устанавливает корень через pivot_root или chroot. - RootEphemeral= — даёт эфемерную копию корня, в которой будет работать сервис (reflink и btrfs). - MountAPIVFS= — создаёт приватное пространство и примонтирует /proc, /sys, /dev и /run (пустой tmpfs), если они не примонтированы. Часто используется вместе с RootDirectory= или RootImage=. - BindLogSockets= — дополнение к предыдущему: монтирует сокеты systemd-журнала. - ProtectProc= — управляет hidepid= для procfs экземпляра и задаёт, какие директории с метаинформацией о процессах будут видны сервису. По умолчанию ограничений нет, но можно скрыть процессы других пользователей или все процессы, которые нельзя ptrace()-нуть. Эта настройка эффективна только при запуске от ненулевого пользователя (User= или DynamicUser=). Есть также похожая опция ProcSubset=. - BindPaths=, BindReadOnlyPaths= — создают bind-маунты; нужно быть аккуратнее: директории должны быть доступны и на них не должно стоять конфликтующих Protect* опций. Переменные, связанные с пользователями: - User=, Group= — запуск от других пользователей. По умолчанию системный сервис стартует от root (если это не user-unit). - DynamicUser= — одна из самых интересных опций: создаёт временного пользователя (с именем юнита), который не записывается в /etc/passwd или /etc/group и удаляется при остановке сервиса. При этом автоматически включаются защитные опции: RemoveIPC=, NoNewPrivileges=, RestrictSUIDSGID=, а также ProtectSystem=strict и ProtectHome=read-only (можно разрешать через StateDirectory=, CacheDirectory=, LogsDirectory=). Динамические UID/GID выделяются из диапазона 61184…65519. Можно одновременно задавать User=/Group=, но указанные имена не должны существовать заранее. - SetLoginEnvironment= — по умолчанию true, контролирует установку переменных окружения $HOME, $LOGNAME и $SHELL. - PAMName= — регистрирует процесс как PAM-сессию; полезно в сочетании с User=. Переменные, связанные с безопасностью: - NoNewPrivileges= — гарантирует, что процесс и его потомки не смогут получить новые привилегии через execve(). Не защищает от взаимодействия с внешними IPC-сервисами, которые сами способны повысить привилегии. - SecureBits= — принимает keep-caps, keep-caps-locked, no-setuid-fixup, no-setuid-fixup-locked, noroot и noroot-locked. - Также есть опции SELinuxContext=, AppArmorProfile=, SmackProcessLabel=. Переменные-свойства (лимиты и приоритеты): - LimitCPU=, LimitFSIZE=, LimitDATA=, LimitSTACK=, LimitCORE=, LimitRSS=, LimitNOFILE=, LimitAS=, LimitNPROC=, LimitMEMLOCK=, LimitLOCKS=, LimitSIGPENDING=, LimitMSGQUEUE=, LimitNICE=, LimitRTPRIO=, LimitRTTIME= — поддерживают мягкие и жёсткие лимиты. - KeyringMode= — управление kernel session keyring: можно выделить новую связку ключей или подключить пользовательскую. - OOMScoreAdjust= — корректировка OOM-score: от −1000 (практически запрет на убийство) до 1000 (максимальная вероятность убийства). По умолчанию 0. - Nice= — приоритет планирования (от −20 до 19). - CPUSchedulingPolicy= и CPUSchedulingPriority= — политика и приоритет CPU. - CPUSchedulingResetOnFork= — сбрасывать повышенные CPU-приоритеты при fork(). - IOSchedulingClass= — класс планирования I/O: realtime, best-effort, idle (по умолчанию best-effort). - IOSchedulingPriority= — приоритет I/O от 0 (высший) до 7 (по умолчанию 4 для best-effort). Переменные изоляции (нужна поддержка ядра для некоторых опций): - ProtectSystem= — принимает булево или full/strict. Если true, то /usr и загрузчик монтируются только для чтения; в full также /etc читается только для чтения; в strict — вся иерархия доступна только для чтения (за исключением /dev, /proc, /sys, которые отдельно настраиваются). Исключения задаются через ReadWritePaths=. - ProtectHome= — при true директории /home, /root и /run/user становятся недоступными; при read-only — доступны только для чтения; при tmpfs — монтируются временные fs в режиме только для чтения. - RuntimeDirectory=, StateDirectory=, CacheDirectory=, LogsDirectory=, ConfigurationDirectory= — специальные директории (например, RuntimeDirectory по умолчанию /run). - RuntimeDirectoryMode=, StateDirectoryMode=, CacheDirectoryMode=, LogsDirectoryMode=, ConfigurationDirectoryMode= — режимы доступа для этих директорий. - StateDirectoryQuota=, CacheDirectoryQuota=, LogsDirectoryQuota= — квоты, если файловая система поддерживает. - RuntimeDirectoryPreserve= — сохранять RuntimeDirectory= при остановке/перезапуске; по умолчанию директория удаляется при остановке сервиса. - ReadWritePaths=, ReadOnlyPaths=, InaccessiblePaths=, ExecPaths=, NoExecPaths=, TemporaryFileSystem= — ограничение доступа к файловой системе (например, разрешить исполнение только по определённым путям). - PrivateTmp= — создать новое namespace для /tmp и /var/tmp. - PrivateDevices= — изолировать /dev, дать сервису только псевдо-устройства (например /dev/null, /dev/random); включение также задаёт DevicePolicy=closed. - PrivateNetwork= — создать новое сетевое пространство имён. - PrivateIPC= — новое IPC-пространство. - PrivateUsers= — новое пользовательское пространство. - ProtectHostname= — создать новое UTS-namespace; можно указать собственный hostname для юнита; при значении private разрешается изменение hostname внутри namespace. - ProtectClock= — запрещает запись в аппаратные или системные часы. - ProtectKernelTunables= — делает переменные ядра доступными только для чтения. Рекомендуется включать для большинства сервисов. - ProtectKernelModules= — запрещает загрузку модулей ядра (не защищает от автоподгрузки в некоторых сценариях). - ProtectKernelLogs= — запрещает чтение/запись кольцевого буфера логов ядра. - ProtectControlGroups= — ограничивает доступ к cgroups (только для чтения или изоляция). - RestrictAddressFamilies= — ограничивает набор семейств сокетов. - RestrictFileSystems= — ограничивает набор файловых систем, на которых процессы юнита могут открывать файлы; символ ~ инвертирует эффект. Есть предопределённые наборы, например @network или @basic-api (смотреть в systemd-analyze filesystems). - RestrictNamespaces= — ограничивает namespaces. - DelegateNamespaces= — делегирует namespace в пользовательский namespace; принимает идентификаторы типов (cgroup, ipc, net, mnt, pid, uts) или булево. - PrivateBPF= — приватный экземпляр bpffs, чтобы скрыть системный bpffs. - LockPersonality= — блокирует вызов personality(2). - MemoryDenyWriteExecute= — запрещает области памяти одновременно записываемые и исполняемые; несовместимо с ПО, динамически генерирующим код (есть обходы через /dev/shm или memfd_create(), но их тоже можно подавить). - RestrictSUIDSGID= — запрещает попытки установки битов SUID/SGID. - PrivateMounts= и MountFlags= — изолируют юнит в приватном mount-namespace: любые точки монтирования, созданные процессом сервиса, будут приватными и не будут видны хосту. Фильтрация системных вызовов: - SystemCallFilter= — принимает список системных вызовов; по умолчанию блокируемый вызов приводит к SIGSYS, можно изменить через SystemCallErrorNumber=. - SystemCallArchitectures= — задаёт архитектуры, для которых фильтр действует (полезно при кросс-запусках). - SystemCallLog= — перечисляет системные вызовы, которые будут логироваться — полезно для аудита. Переменные окружения: - Environment= — задаёт переменные окружения для сервиса. (Не использовать для передачи секретов.) - EnvironmentFile= — читает переменные из файла; в файле допустимы комментарии ; и #. В мануале также описан весь его синтаксис. - PassEnvironment=, UnsetEnvironment= — передаёт или исключает переменные окружения менеджера сервисов в запускаемые процессы. Общая философия — предоставлять процессам минимально необходимый набор переменных окружения. Для каждого процесса список переменных формируется из следующих источников (в порядке применения/приоритета): - Переменные, глобально настроенные менеджером сервисов через DefaultEnvironment=, опцию командной строки ядра systemd.setenv= или через systemctl set-environment. - Переменные, определённые самим менеджером сервисов. - Переменные, установленные в блоке окружения менеджера сервисов (с учётом PassEnvironment= для системного менеджера). - Переменные, заданные через Environment= в unit-файле. - Переменные, прочитанные из файлов, указанных через EnvironmentFile=. - Переменные, установленные PAM-модулями, если действует PAMName=. Менеджер сеанса определяет переменные: $PATH, $LANG, $USER, $LOGNAME, $HOME, $SHELL, $INVOCATION_ID, $XDG_RUNTIME_DIR, $RUNTIME_DIRECTORY, $STATE_DIRECTORY, $CACHE_DIRECTORY, $LOGS_DIRECTORY, $CONFIGURATION_DIRECTORY, $CREDENTIALS_DIRECTORY и прочие — весь список подробно в руководстве. Переменные для логирования и стандартных потоков: - StandardInput= — куда подключается stdin: null (по умолчанию), tty, tty-force, tty-fail, data, file:path, socket или fd:name. При подключении к TTY используется TTYPath=; tty-force принудительно переключает управляющий TTY; tty-fail как tty, но выдаёт ошибку, если терминал занят. - StandardOutput= — куда подключается stdout: inherit, null, tty, journal, kmsg, journal+console, kmsg+console, file:path, append:path, truncate:path, socket или fd:name. inherit дублирует дескриптор, journal — в systemd-журнал, kmsg — в буфер логов ядра. - StandardError= — для stderr, список идентичен. - StandardInputText=, StandardInputData= — для передачи текстовых/двоичных данных в stdin. - LogLevelMax= — максимальный уровень логирования: emerg, alert, crit, err, warning, notice, info, debug. Помогает отсеивать лишние сообщения. - LogExtraFields= — дополнительные поля метаданных журнала. - LogRateLimitIntervalSec=, LogRateLimitBurst= — ограничение скорости логов; использовать аккуратно, можно потерять важные сообщения. - LogFilterPatterns= — фильтрация логов по паттернам. - LogNamespace= — запуск в отдельном namespace журнала. - SyslogIdentifier= — syslog tag (по умолчанию — имя процесса). - SyslogLevel= — уровень syslog: emerg, alert, crit, err, warning, notice, info, debug. - TTYPath=, TTYReset=, TTYVHangup=, TTYColumns=, TTYRows= — настройка TTY. Из других интересных фактов: - chroot не обеспечивает изоляцию, а только меняет корневой каталог. - Надо быть внимательнее с drop-in файлами systemd: часто забывал переписывать старую переменную, и новая переменная лишь дополняла существующую. Существует также systemctl show/systemd-delta - Для многих операций с пользователями требуется включённый kernel.unprivileged_userns_clone. - Для загрузки учётных данных существуют LoadCredential=ID[:PATH], LoadCredentialEncrypted=ID[:PATH] — один из безопасных способов передачи секретов, подробнее также описано в руководстве.
Изучил systemd.target. Если говорить кратко – это просто определённая цель, при помощи которой можно управлять зависимостями или запускать группу служб. Его часто используют в других службах, чтобы дождаться, когда запустится определённая группа служб, например, network-online.target Также изучил systemd.preset. У него очень простой синтаксис (enable, disable или ignore), его основная цель это определить то, какие юниты должны быть включено по умолчанию, а какие отключены. Некоторые дистрибутивы могут класть пресеты вместе с установкой пакетов. Вновь экспериментировал с DynamicUser= и ReadySet, похоже определённая изоляция не даёт пока запустить. Разбираюсь с этим. Параллельно изучил, как работают systemd kill и как systemd их контролирует.
Появился значительный прогресс. Сервисный пользователь временно откладывается из-за проблем в самом ReadySet. Оконный менеджер Mutter, к сожалению, оказался не самым удачным выбором, хоть он и умеет работать из под root, но запускать его из под systemd задача очень неблагодарная. По неопределённым причинам Mutter выдаёт сбой графики и после запуска сразу же зависает, если пытаться запустить его не в открытой сессии с готовым XDG_SESSION_ID. Смена бэкенда отрисовки не помогало, как и подключение PAMName=login (это важно, потому что при помощи PAM можно инициализировать и начальные переменные окружения). Я мог это реализовать через автовход в getty, но если что-то пойдёт не так, то в системе может остаться потенциальная уязвимость. Примечательно, что другие оконные менеджеры такого не показывают со своей стороны. Подозреваю, что Mutter очень зависит от правильной инициализации PAM, графики и других важных переменных, что обычно делает GDM при старте (но тащить его ради ReadySet было бы нецелесообразно). В то время как systemd предоставляет довольно чистую от параметров окружения среду (это была одна из причин, почему я начал такое плотное изучение данной системы инициализации) Я протестировал Weston и заметил, что он лёгкий и работает отлично как на программной графике, так и на Intel встройке. Тестирование NVIDIA видеокарты предстоит, но я уверен, что там всё хорошо (исходя из отзывов). Weston отлично запускается из под systemd и из под сервисных пользователей. На данный момент Weston будет работать от рута (из-за ReadySet). По умолчанию он запускается в режиме рабочего стола, но это можно исправить через weston.ini конфиг, который я поместил в папку вместе с ready-set-services (/usr/share/ready-set-service/weston.ini). В конфигурации я прописал автоматический запуск ReadySet и отслеживание, когда он завершит свою работу (watch=true, иначе Weston будет работать вечно). Без PAM-инициализации также очень странно захватывались tty, поэтому я добавил PAMName=login. Поменял service-файл, который ранее использовался для Cage. Теперь блок ExecStart отвечает только за запуск Weston с ReadySet, а для выполнения операций после завершения ReadySet используется уже другой скрипт, прописанный уже в ExecStop. Он выполняет: 1. Удаление systemd.unit=setup.target из параметров ядра в GRUB (он будет прописан там после установки системы, чтобы запускался наш ReadySet) 2. Удаление weston.desktop сессии, это нужно для следующего шага 3. Запуск текущего Display Manager. Как раз прошлый шаг и нужен был, чтобы пользователь не мог зайти в Weston. Ранее у нас выполнялась команда перезагрузки, но я считаю это действие не особо нужным, ибо вход в нового пользователя и так по новой всё инициализирует. Единственное, возможно текущий уровень выполнения systemd нужно сразу менять, а не после перезагрузки, но я потом внимательнее это рассмотрю. 4. Обновление GRUB и удаление Weston, ready-set и ready-set-service. Последние операции я изначально хотел сделать асихронными, но я не проверял, как на такое реагирует systemd. Поэтому оставил операции последовательными, чтобы их нельзя было так легко прервать условным выключением системы. Мне не очень нравится текущий костыль с удалением параметра ядра из GRUB и его обновлением, по-хорошему у нас для этого есть `systemctl set-default`, но это не приоритетная задача на данный момент и к этому можно будет вернуться позже. Есть также проблемы с тем, что некоторые лишние зависимости всё равно остаются. В теории можно было бы использовать `apt-get autoremove`, но это более долгая операция, поэтому не стоит. Но текущее решение работает без критических проблем, проверял на разных сборках. Есть некоторые вещи, которые мне ещё не нравятся, но их решить возможно. Например, сейчас нельзя переключать раскладки (но конфигурацию для этого я подготовил), а также в /root директории появляются GTK-конфиги (это можно исправить средствами systemd). Довольно много времени также потратил на проверку разных систем изоляции, чтобы это было достаточно безопасно и не оставляло всякий хлам в системе. Я попробую их встроить, когда выйдет новая версия ReadySet (ибо изменений там тоже хватает). Вот текущий Merge Request: https://gitlab.eterfund.ru/ximperlinux/ready-set-service/merge_requests/1 Потратил довольно много времени на изучение systemd и некоторых его особенностей. Например, освоил инструмент systemd-nspawn в качестве более лучшей альтернативы chroot, созданная для отладки и тестирования. Узнал про разницу /etc/default и /etc/sysconfig, изучал параметры agetty и практиковал на них знания по systemd шаблонам (а ещё понял, почему systemd может экранировать некоторые элементы). Узнал, как эффективнее пользоваться journalctl, получать дополнительные метаданные в логах и лучше сортировать по определённым службам. Кажется, Weston поддерживает программный сторожевой таймер, который можно будет интегрировать со службой на случай, если оконный менеджер зависнет. Я попробую протестировать это чуть позже. Изучил ограничение ресурсов через cgroups и игрался с DynamicUser. Всё таки сервисного пользователя через это я думаю можно будет создать в будущем. Описания некоторых параметров окружений, которые полезно знать: - $XDG_SESSION_ID - короткий идентификатор сеанса, подходящий для использования в именах файлов. Каждый ID будет назначен только один раз во время работы машины. Следовательно, его можно использовать для уникальной маркировки файлов или других ресурсов сеанса. Без этой переменной рабочие окружения не понимают, за что им зацепляться и где запускаться. В особенности Mutter. Но с этой задачей инициализации спокойно справляется и PAM. - $XDG_RUNTIME_DIR - путь к приватному для пользователя и доступному для записи каталогу, который привязан ко времени входа пользователя на машину. Он автоматически создаётся при первом входе пользователя и удаляется при окончательном выходе пользователя. Если не инициализировать PAM (или запускать сервис в изолированном окружении), то может быть полезным указывать. - $XDG_AREA - Если была выбрана область (area) для входа (вторичные домашние каталоги пользователя внутри основного домашнего каталога), эта переменная устанавливается в имя области (без какого-либо префикса пути). В противном случае она не установлена. Принимает в качестве параметра имя файла. Если указано и пользователь входит в свою учётную запись, переменная окружения $HOME будет установлена в ~/Areas/, дополненное указанной строкой, но только если этот каталог существует. Эта функциональность может использоваться для поддержания нескольких отдельных вторичных домашних каталогов внутри основного домашнего каталога пользователя. Обычно область (area) для входа указывается во время входа, если учётная запись это разрешает (учётные записи, предоставляемые pam_systemd_home(8), делают это), но этот параметр может использоваться для определения области по умолчанию, если она не указана.
Провёл тестирование своего Merge Request с последней версией ready-set 0.3.1. Теперь не работает, ReadySet в целом не запускается, ибо ему нужна предварительная настройка. Займусь позже. > (ready-set-ruler:4156): org.altlinux.ReadySet-ERROR **: 02:16:23.748: utils.vala:90: Failed to generate rules: Error opening directory '/usr/share/ready-set/rules.d': No such file or directory > > (ready-set:4064): org.altlinux.ReadySet-ERROR **: 02:16:23.748: application.vala:85: Failed to generate rules: Дочерний процесс убит по сигналу 5
Пришлось исследовать новое обновление, выяснил многое. Начнём с того, что теперь нужно вручную настраивать конфиг-файл или передавать определённые плагины и параметры через CLI. Плагины есть уже в репозитории, например: - ready-set-plugin-keyboard - плагин выбора раскладки клавиатуры - ready-set-plugin-language - плагин выбора языка - ready-set-plugin-user-passwdqc - плагин создания пользователя (passwdqc) - ready-set-plugin-user-pwquality - плагин создания пользователя (libpwquality) - ready-set-plugin-welcome - приветственная страница Файл конфига загружается из: - /etc/ready-set/config - /usr/share/ready-set/config Плагины ищутся в: - $LIBDIR/ready-set/plugins/ (.plugin + .so) - $DATADIR/ready-set/plugins/ (метаданные) Так что при желании можно написать свои плагины. Изучил CLI: -s, --steps=STEPS - Порядок этапов (через запятую) --steps-no-apply=STEPS_NO_APPLY - Этапы, но без применения -c, --context=CONTEXT - Контекстные переменные (key=value) -C, --conf-file=CONF-FILE - Путь к конфигу -i, --idle - Запуск без применения изменений -F, --fullscreen - Полноэкранный режим (как раз в новой версии добавили) -u, --user=USER - Пользователь для polkit-правил В основном полезно знать CLI для тестов. Теперь про контекстные переменные. Они задаются в секции [Context] конфига или через -c key=value в CLI: - user-with-root (bool) - показывать поле пароля root - hide-autologin (bool) - скрыть переключатель автологина - no-password-security (bool) - отключить проверку сложности пароля - passwd-conf-path (string) - путь к конфигу passwdqc (по умолчанию /etc/passwdqc.conf) Ещё появился новый бинарник: /usr/libexec/ready-set-ruler. Он управляет polkit-правилами для плагинов: --generate-rules -u <user> - копирует шаблоны из /usr/share/ready-set/rules.d/ в /etc/polkit-1/rules.d/, заменяя --READY-SET-USER-- на указанного пользователя --clear-rules - удаляет сгенерированные правила --restart-polkit - перезапускает polkit после изменений Каждый плагин при установке кладёт шаблон в /usr/share/ready-set/rules.d/: - Keyboard - org.freedesktop.locale1.* - Language - org.freedesktop.locale1.* - User - org.freedesktop.accounts.*, запуск ready-set-set-root-password Polkit-правила не нужны при запуске от root. Polkit по умолчанию разрешает всё для uid 0. Правила нужны только для непривилегированных пользователей. Я думаю нужно переспросить Рирушу, точно ли невозможно пользоваться ReadySet без root прав. В ready-set-service я учёл новую систему, теперь через CLI я подгружаю нужные плагины и это работает. Плюс скорректировал зависимости. В ближайшее время обновлю MR. Но пришлось учесть ещё один фактор и он связан с плагинов keyboard. Плагин keyboard зависает, а ранее он не работал корректно на других рабочих окружениях. То есть он был создан исключительно под GNOME. В качестве временного варианта, я в ready-set-service скрыл плагин keyboard для других окружений. Но как я понял, это поправимо. Если интересно, то вот почему происходит зависание ReadySet с keyboard плагином: Функция get_current_inputs() (plugins/keyboard/utils.vala:116-128) при отсутствии контекстной переменной keyboard-input-sources обращается к GSettings: > var settings = new Settings ("org.gnome.desktop.input-sources"); > var variant = settings.get_value ("sources"); На KDE/Hyprland схема org.gnome.desktop.input-sources отсутствует или не отвечает. Хотя плагин должен работать на других окружениях: page.vala:67-76 вызывает set_x_11_keyboard через D-Bus (org.freedesktop.locale1), что работает на многих DE, но запись в GSettings всё равно происходит. Ещё сравнил отличие USER-PASSWDQC и USER-PWQUALITY. В действительности оба плагины одновременно ставить нельзя, это тоже баг, эти пакеты должны быть либо помечены как конфликтные, либо ReadySet должен выдавать предупреждение. Оба плагина регистрируют один и тот же GType UserAddin из user-common. При одновременной установке возникает: > GLib-GObject-CRITICAL: cannot register existing type 'UserAddin' Разница между ними минимальна, но libpasswdqc уже предустановлен в сборки, поэтому будем использовать его. У USER-PASSWDQC нет прогресс-бара и оценка более бинарная (BAD/GOOD), в то время как у pwquality оценка проходит от 0 до 100 и есть прогресс-бар. Но это всё бессмысленно и переусложняет только настройку, поэтому я просто отключу проверку пароля. Посмотрел также реализацию автологина в новой версии. Реализован в plugins/user/plugin.vala:52: > user.set_automatic_login (context.get_boolean ("user-autologin")); Вызывает метод AccountsService D-Bus, который записывает автологин в конфиг дисплейного менеджера (GDM/LightDM/SDDM), если память не изменяет. Предстоит проверить. Также выяснил, что alterator-xkb (в установщике) при выборе раскладке записывает данные в /etc/X11/xorg.conf.d/00-keyboard.conf. Если это остаётся в установленной системе - то в теории это можно использовать для переключения раскладки в Weston, но пока я оставил Alt+Shift с русской и английской раскладкой.
> Также выяснил, что alterator-xkb (в установщике) при выборе раскладке записывает данные в /etc/X11/xorg.conf.d/00-keyboard.conf. Если это остаётся в установленной системе - то в теории это можно использовать для переключения раскладки в Weston, но пока я оставил Alt+Shift с русской и английской раскладкой. Странно, в ALT Regular KDE это присутствует, но в нашей сборке этого нет (отсутствует именно комбинация клавиш). Постараюсь расследовать. В остальном текущие изменения работают как надо, также исследовал, насколько трудным будет написать свой модуль клавиатуры для Hyprland и KDE Plasma, так как вариант в альтераторе мне совершенно не нравится из-за привязанности к X11. Для написания плагинов нужно знать Vala, GTK4/Libadwaita и Blueprint (декларативное описание UI). Архитектура простая, оно базируется на libpeas-2, реализовывается метод build_pages() и точка входа peas_register_types(). В конечном итоге можно адаптировать и текущий keyboard плагин.
Спустя долгое время (приношу искреннее прощение за долгий перерыв, берусь за активную работу) смотрю на изменения в ReadySet и обновляю конфиги. В новой версии ReadySet 0.6.2 плагины теперь лежат не в $LIBDIR/ready-set/plugins/, а разделены по типам: - step-плагины: $LIBDIR/ready-set/plugins/steps - installer-плагины: $LIBDIR/ready-set/plugins/installers Изменился также CLI: В старом отчёте были устаревшие опции: -i, --idle теперь не idle, а -i, --sandbox -u, --user в текущем CLI нет Остальные параметры без изменений. Ранее в отчёте был описан механизм ready-set-ruler, который генерировал polkit-правила под пользователя. В текущем ReadySet 0.6.2 этот механизм в исходниках не используется: polkit-правила поставляются пакетами, а доступ завязан на группы ready-set и gnome-initial-setup. Поэтому сервис, скорее всего, нужно запускать ReadySet с SupplementaryGroups=ready-set. В старом отчёте также фокус был на root/polkit. В текущем ReadySet root сам по себе не переводит приложение в initial setup mode. Сейчас режим выбирается так: - INSTALLER, если указан installer plugin - INITIAL_SETUP, если процесс состоит в группе ready-set или gnome-initial-setup иначе TOUR Поэтому для ready-set-service нужно не только запускать от root, но и добавлять: > SupplementaryGroups=ready-set Иначе ReadySet может запуститься не как первичная настройка, а как обычный tour. Изменились также context-переменные плагинов, но мы пока своих плагинов не писали, поэтому их изменения перечислять не вижу смысла. Плагин клавиатуры тоже изменился, но он всё ещё неудобен для нашего сценария В ReadySet 0.6.2 плагин клавиатуры теперь берёт xkb-model и xkb-options из GNOME GSettings org.gnome.desktop.input-sources и передаёт их в org.freedesktop.locale1.SetX11Keyboard. Но проблема для ready-set-service остаётся другая: - Плагин всё ещё читает и пишет GNOME GSettings; - keyboard-input-sources теперь context-переменная типа OBJECT, её нельзя нормально задать простым INI-конфигом; Я думаю пока просто задать раскладку us,ru и поставить переключатели Alt+Shift, Meta+Space. Буду смотреть.
Доработал ready-set-service, тестирую, сделаю мини-рефакторинг и отправляю PR. Добавил в ready-set-service некоторые зависимости в виде плагинов, как писал ранее в отчётах. На момент сборки в Deferred был старый ready-set и отсутствующие некоторые плагины, поэтому сборка ISO завершалась ошибкой. Обошёл это, временно собрав всё нужное в хешере. Столкнулся с очень странным багом, после установки мой GRUB оказался битым. Изначально подумал. что это мой косяк, но нет, это какой-то фантомный баг. В /boot/grub/grub.cfg по каким-то причинам записался список файлов! > /tmp/.private/.../stage1/scripts.d/01-syslinux > /tmp/.private/.../stage1/scripts.d/02-gfxboot > ... Виновнив явно features.in/grub/stage1/scripts.d/01-grub, но почему это произошло - я не до конца понял. Похоже на какую-то гонку, ибо после пересборки этот баг уже не воспроизвёлся. Но нужно иметь в виду. После столкнулся с другой проблемой: ready-set-service не запустился. Чтобы он запустился, как я до этого его написал, нужен ` systemd.unit=setup.target`. Но его не было в установленной системе. Покопавшись увидел, что я добавил переменную в `BASE_BOOTARGS` и до этого всё работало, но сейчас почему-то не работает. Спустя время выяснил, что это из-за того, что наш метод установки изменился. Если раньше у нас установщик ставил пакеты, то теперь он распаковывает live и BASE_BOOTARGS не применяется. Написал новый скрипт, который должен начать применять BASE_BOOTARGS и для нашего live-install. Возможно это немного грубоватый хук, но он работает. Во время финализации я ещё посмотрю, что можно сделать. Вообще с BASE_BOOTARGS своя путаница. Про него нигде не написано, написано только про BOOTARGS и STAGE2_BOOTARGS. После этого ReadySet запустился. Сразу столкнулся с тем, что включилась фильтрация пароля, которую ранее я уже отключал. Оказалось, что переменные отличаются в новой и старой версии, поэтому пришлось доработать. Старые (0.6.2): > no-password-security=true > passwd-conf-path=/etc/passwdqc.conf Новые (из main): > user-no-password-security=true > user-passwd-conf-path=/etc/passwdqc.conf Пока указал две пары, ибо в последующих тестах я использовал ReadySet из Сизифа, а не из последних исходников. Но для будушего понадобится. Правда после ввода пароля ReadySet крашнулся. Оказалось, когда ReadySet доходит до конца, он падает на welcome. Потому что этот плагин можно показывать, но он в настройке был применён как setup-step. Добавил его в steps-no-apply. В коде это буквально так: welcome умеет строить страницу в build_pages(), но его apply() содержит assert_not_reached() в plugin.vala. То есть авторы явно заложили: если кто-то попытается “применить” welcome, это ошибка программы. В версии 0.6.2 также текст welcome был на английском языке, но в новых версиях это исправлено. Проверил, плагин language влияет на конечную LANG переменную. Разобрался, как установщик добавляет раскладки в систему. Языки и раскладки отображаются через alterator-sysconfig UI. Как я понял, в обычном ALT Regular и других сборках он копирует ещё раскладку в `/usr/share/alterator-sysconfig/data/kbd/`. Поэтому я сначала хотел читать раскладку через `alterator-sysconfig-functions` и даже сделал набросок, но совсем не учёл, что наш установщик стирает все alterator пакеты. Поэтому следом за ним улетал и ready-set-service, ReadySet и система уходила в rescue. Разобрался ещё, увидел, что установщик также записывает раскладки в /etc/X11/xorg.conf.d/00-keyboard.conf. даже после удаления Xorg этот файл останется, поэтому это нам на руку. Забавный факт, но KDE Plasma умеет считывать этот файл и поэтому добавляет оттуда раскладки :) Теперь разбор /etc/X11/xorg.conf.d/00-keyboard.conf: Ожидаемый формат файла: > Section "InputClass" > Identifier "system-keyboard" > MatchIsKeyboard "on" > Option "XkbLayout" "us,ru" > Option "XkbVariant" "," > Option "XkbOptions" "grp:alt_shift_toggle,compose:menu" > EndSection Что читать: 1. XkbLayout > Option "XkbLayout" "us,ru" 2. XkbVariant > Option "XkbVariant" "," Запятая без значений нормальна. Она означает: пустой variant для us и пустой variant для ru. 3. XkbOptions > Option "XkbOptions" "grp:alt_shift_toggle,compose:menu" Это главное поле. В нем надо искать token вида grp:*. Alt+Shift: grp:alt_shift_toggle Пример: > Option "XkbOptions" "grp:alt_shift_toggle,compose:menu" Для пункта "Ctrl" в alterator-sysconfig используется именно grp:rctrl_toggle, а не grp:ctrl_toggle. И так далее. Сводная таблица: > Alt+Shift grp:alt_shift_toggle > CapsLock grp:caps_toggle > Ctrl+Shift grp:ctrl_shift_toggle > Ctrl grp:rctrl_toggle > Alt grp:toggle Сделал генератор weston.ini, который перед запуском ReadySet парсит всё это и настраивает раскладку в Weston. Ещё в качестве резервной раскладки по умолчанию сделал Meta+Space. Выяснил, можно ли отображать уведоления в Weston. Увы, встроенными средствами в weston.ini нельзя. Ещё дописал генератор и сделал включение keyboard-плагина в ReadySet для GNOME (для остальных DE он не рабочий). Наличие GNOME проверяю по наличию пакета gnome-shell. Также выяснил, какой установщик сейчас в Ximper. У нас установщик `installer-distro-ximper`, основанный на установщике ALT Workstation. Установщик девятой версии, десятая версия хранится в GitLab репозитории и уже содержит патч на отключение странички создания пользователя в установщике. Собрал в хешере, всё работает как надо. Единственное, уже 11 версия оригинального установщика есть, надо рассмотреть, может что-то ещё полезное там есть. Но это позже. В целом всё готово, уже протестировал с разными комбинациями и работает. Остаётся некоторая полировка.
Цель: довести первый запуск ReadySet в Ximper/ALT до предсказуемого состояния: чтобы сервис действительно стартовал при первой загрузке, запускал Weston/ReadySet, корректно завершал setup только после создания пользователя и не оставлял систему в полусломанном состоянии. Отдельно разбирался со сборкой образа. Сборка несколько раз падала в разных местах, и важно было не смешивать старые причины с новыми. Один из прежних сбоев выглядел как проблема fakeroot: внутри fakeroot каталог /.our виделся как обычный файл, из-за чего pack-image падал с cd: /.our: Not a directory. Позже сборка дошла до другого слоя и упала уже на hsh-install: Packages installation failed. В логе было видно, что rpm не хватает места: needs 7MB on the / filesystem. Проверка df показала /tmp/.private/katze на 100%: 168G занято, свободно около 51M. Причиной оказались старые ximper-builder.* builddir/snapshot-каталоги, часть из них занимала примерно по 17G. Обычный rm и hsh --cleanup-only не решили проблему полностью, потому что настоящие hasher workdir лежали глубже, например в stage1/.work и live/.work. Пришлось чистить вложенные .work через hsh-rmchroot --wait-lock, а потом удалять оставшиеся /tmp/.private/katze/ximper-builder.*. После этого matching-каталогов ximper-builder.* больше не осталось, а свободное место выросло примерно до 121G. То есть текущий hsh-install failure был не из-за ReadySet и не из-за зависимостей пакетов, а из-за закончившегося места. Дальше я чинил сценарий завершения ReadySet. Изначально post-cleanup удалял /usr/share/wayland-sessions/weston.desktop, запускал обратно обычную графическую цель/display-manager, обновлял GRUB через update-grub и выполнял apt-get remove временных setup-пакетов вроде weston, libweston, ready-set и ready-set-service. В тестах вскрылась серьёзная проблема: ReadySet мог завершиться до создания пользователя, после чего destructive cleanup всё равно начинался. На одном из прогонов графика ReadySet запускалась, но примерно через полминуты сессия завершалась (это отдельная проблема с крашем ReadySet, с которой я ещё разбираюсь). После этого ready-set-post.sh пошёл в cleanup, но systemd убил ExecStop по timeout: ready-set-service.service: Stopping timed out. Terminating, Control process exited, code=killed, status=15/TERM. Затем из-за Restart=on-failure systemd попытался перезапустить сервис, но cleanup уже успел удалить /usr/bin/ready-set-service. В итоге появлялась вторичная ошибка Unable to locate executable '/usr/bin/ready-set-service' и status=203/EXEC. Чтобы это исправить, я добавил проверку, создался ли обычный пользователь. Появился helper ready-set-user-exists.sh: он считает setup успешным только если в passwd есть login-пользователь с UID в нормальном диапазоне, home в /home и нормальным shell, не nologin/false/dev/null. В ready-set-post.sh эта проверка стоит до GRUB, update-grub, apt-get и запуска graphical target. Если пользователя нет, post-скрипт выходит с кодом 75, не трогает bootarg, не удаляет пакеты и даёт systemd перезапустить ready-set-service. Если пользователь есть, cleanup продолжается как раньше. В unit добавил RestartSec=5s и TimeoutStopSec=15min, чтобы долгий apt-get remove или другой cleanup не убивался слишком рано. Проверил основную логику: без пользователя cleanup завершается до destructive-части и не трогает GRUB/пакеты, а при наличии нормального пользователя может продолжать штатное завершение. Отдельно прорабатывал порядок cleanup. apt-get remove может идти долго, и если выполнять его до запуска обычной графической загрузки, пользователь будет долго видеть чёрный экран с логами, что просто раздражает. Поэтому удаление setup-пакетов перенёс в самый конец: сначала нужно проверить, что пользователь создан, убрать systemd.unit=setup.target из GRUB, запустить нормальный graphical target/display-manager, а уже после этого удалять временные setup-компоненты. При этом отдельно рассматривал вариант запустить apt-get remove в фоне через &, чтобы команда не задерживала сервис. От этого отказался: в таком случае systemd пометил бы ready-set-service как завершённый, и при попытке выключить систему уже не стал бы ждать окончания apt-get remove. Без & сервис продолжает работать до тех пор, пока удаление пакетов не закончится, поэтому shutdown корректно ждёт cleanup. Для ускорения cleanup я убрал долгий update-grub из ready-set-post.sh. Вместо полной регенерации GRUB скрипт теперь правит source-конфиг /etc/sysconfig/grub2, удаляя systemd.unit=setup.target из GRUB_CMDLINE_LINUX*, и отдельно патчит уже сгенерированный grub.cfg. Путь к generated-конфигу берётся из GRUB_AUTOUPDATE_CFGNAME, а если его нет, используется /boot/grub/grub.cfg. Сначала рассматривались linuxefi/linux16 и EFI-ветки, но после проверки ALT-генераторов /etc/grub.d/10_linux и /etc/grub.d/30_os-prober оставил только plain linux-строки. Проверял bash -n, shellcheck и dry-run на временных grub-файлах: token удаляется из linux-строк, но не трогает menuentry и комментарии. Также рассматривал вариант не удалять ReadySet и Weston после первого запуска, а оставить их в системе как fallback: если обычного пользователя снова нет, система могла бы снова загрузить setup-интерфейс. На практике эта идея оказалась слишком проблемной. ReadySet запускает Weston напрямую, без display-manager, поэтому для такого fallback Weston runtime должен оставаться установленным. Но обычный пакет Weston также кладёт /usr/share/wayland-sessions/weston.desktop, из-за чего display-manager начинает показывать Weston как отдельную пользовательскую сессию. Для Ximper это лишняя и потенциально путающая сессия. Простое удаление weston.desktop — плохой вариант: обновление пакета может вернуть файл, а если пользователь сам захочет поставить Weston как обычную сессию, мы начнём ломать ожидаемое поведение пакета. Можно было бы удалять Weston после первого запуска и ставить его обратно только если fallback снова понадобится, но это тоже ненадёжно. В состоянии “пользователя нет” нельзя гарантировать доступ к сети и репозиториям. Особенно с Wi-Fi: сеть поднимется без пользователя только если подключение сохранено как system-wide NetworkManager connection с autoconnect=yes. Если Wi-Fi был user-owned или секрет лежал в пользовательском keyring, после удаления пользователя сеть может не подняться, а значит восстановить Weston/ReadySet из репозитория уже не получится. В итоге я отказался от идеи постоянного fallback через оставленные ReadySet и Weston. Она требует отдельной пакетной схемы для Weston session, зависит от состояния сети и добавляет много пограничных случаев. Вместо этого оставил более простой путь: ReadySet удаляется после успешного создания пользователя, но destructive cleanup запускается только после проверки, что пользователь действительно появился. Отдельно чинил клавиатуру и Meta+Space. Сначала был вариант читать X11-конфиг из /etc/X11/xorg.conf.d/00-keyboard.conf через отдельный ready-set-weston-config.sh и генерировать Weston config на лету. Но потом выяснилось, что выбора раскладки в установщике не будет, поэтому этот вариант я сохранил в отдельной ветке archive/runtime-x11-keyboard-config с коммитом c75c6f4, а на master вырезал runtime-чтение X11-конфига. После упрощения ready-set-service снова запускает packaged weston.ini напрямую, ready-set-start.sh всегда пишет steps=welcome,language,user-passwdqc без keyboard-плагина, Makefile больше не устанавливает ready-set-weston-config.sh, а из spec убран Requires: ready-set-plugin-keyboard. Keyboard-плагин отключил временно: сейчас он проблемно подходит для нашего Weston-сценария, так как завязан на GNOME/GSettings, а точное поведение в этой связке ещё нужно отдельно разобрать. Когда разберусь с этой проблемой, верну выбор раскладки обратно. В weston.ini оставил фиксированные раскладки us,ru и набор переключателей: Ctrl+Space, Alt+Space, Win/Meta+Space, Alt+Shift, Ctrl+Shift, плюс compose:menu. Для Meta+Space используется XKB option grp:win_space_toggle, потому что Win в XKB соответствует Meta/Super. До этого я также проверял вариант через localectl и ReadySet keyboard plugin. У ReadySet 0.6.2 keyboard plugin завязан на GNOME GSettings org.gnome.desktop.input-sources и не подходит как универсальный механизм для Weston. Weston не читает эти GNOME-настройки, а берёт keymap из своего weston.ini при старте. Поэтому для первого запуска на Weston надёжнее временно отключить keyboard-плагин, а раскладки и переключатели задавать на уровне Weston. Для persistent-настройки клавиатуры в уже установленной GNOME/KDE/Hyprland-сессии это отдельная задача, не часть ready-set-service. Проверял и вопрос с sysconfig-kbd/sysconfig-language: хотелось понять, можно ли положить эти настройки и не потянет ли это за собой Xorg. В рабочем варианте этот путь не стал основным. Отдельных пакетов с такими именами в локальном apt не нашлось, а тянуть alterator-sysconfig-functions ради этой задачи я не стал. В итоге в зависимостях оставил xkeyboard-config и ушёл от ready-set-plugin-keyboard/alterator-sysconfig-обвязки. Ещё долго разбирался с изоляцией ready-set-service. Сейчас сервис остаётся root-сервисом с дополнительной группой ready-set и ограничениями через systemd sandbox: так проще сохранить работу Weston, ReadySet, polkit и post-cleanup. Отдельно рассматривал, можно ли уйти от root-запуска всей графической сессии к более ограниченному сервису. DynamicUser для этого не подходит: ReadySet должен попадать в группу ready-set, polkit/ruler должны видеть ожидаемый subject, а графическая сессия требует нормальных PAM/logind/TTY условий. Если позже переводить основную Weston/ReadySet-сессию на non-root, то более правильный вариант — статический User=ready-set и Group=ready-set, сохранение PAMName=login, TTYPath=/dev/tty1, StandardInput=tty, systemd-managed RuntimeDirectory=ready-set-service и writable state/home для /var/lib/ready-set, GTK, dconf и cache. При проработке sandbox столкнулся с тем, что слишком жёсткая изоляция ломает установку root-пароля. ready-set-set-root-password делает echo "root:$1" | /usr/sbin/chpasswd, а на ALT/tcb при ReadOnlyPaths=/etc возникала ошибка Authentication token lock busy, даже если /etc отдельно указывался в ReadWritePaths. То есть нельзя просто положить /etc в read-only слой и выборочно открыть его обратно. Рабочая идея sandbox стала мягче: ReadOnlyPaths=/usr -/boot, а writable оставить для /var/lib/ready-set, /run/ready-set-service, /run/user и /etc. Проверял и прямой запуск через pkexec, но вариант ExecStart=/usr/bin/pkexec оказался неправильным для systemd-сервиса: pkexec отказывался работать с сообщением Refusing to render service to dead parents. Поэтому такие проверки надо делать через wrapper/child-процесс как в реальном ReadySet, а не прямым ExecStart=pkexec. Для будущего non-root варианта отдельно зафиксировал, что root-cleanup лучше переносить из ExecStop в ExecStopPost=+/usr/share/ready-set-service/ready-set-post.sh: основная Weston/ReadySet-сессия сможет работать от ready-set, а post-setup cleanup всё равно выполнится с root-правами. Но apt-get remove ready-set-service из post-скрипта самого этого пакета остаётся хрупкой схемой. Более надёжное направление — отдельный transient cleanup unit через systemd-run --unit=ready-set-package-cleanup --collect --no-block, после того как bootarg cleanup уже сделан. При проверке сборки ready-set-service отдельно следил, чтобы изменения попадали в пакет и не ломали unit/post-скрипты. Во время сборки также всплывало предупреждение bogus date in %changelog для Fri Feb 14 2026 — сборку оно не ломало, но перед финальным релизом это надо будет поправить. Итоговое состояние: ReadySet должен стартовать только при загрузке через setup.target, Weston запускается напрямую как kiosk-shell с заранее заданной раскладкой us,ru, Meta+Space и другие переключатели заданы в weston.ini, keyboard-плагин временно убран из первого запуска, cleanup не выполняется без созданного пользователя, долгий update-grub заменён точечным патчем GRUB, а timeout/retry логика защищает систему от удаления setup-пакетов в середине неуспешного запуска.
Цель: разобраться с проблемой смещения курсора в ReadySet под Weston и проверить рабочий обход без патча системного Weston. Проблема проявлялась так: после ввода текста видимый курсор мыши и реальная позиция pointer/hitbox расходились. Особенно хорошо это воспроизводилось на шаге ReadySet «Имя пользователя»: нужно было начать печатать имя, а затем резко увести курсор вниз. После этого видимый курсор оказывался не там, где была фактическая точка клика, из-за чего его нельзя было нормально прижать к нижнему краю экрана и было трудно попадать по кнопкам. Сначала я проверял самые очевидные версии: разрешение, несколько мониторов, VM и особенности конкретной тестовой среды. Менял конфигурацию VM: оставлял один монитор в HD, потом ставил 3.1K, потом возвращал Full HD. Из-за недостаточно точного сценария сначала могло показаться, что поведение меняется от конфигурации, но позже стало понятно, что это не привязано к конкретному разрешению. Также подтвердилось, что проблема не связана и с количеством мониторов: она проявлялась и на одном экране, а также встречалась на реальном железе. Первой сильной гипотезой был Weston DRM hardware cursor plane. ReadySet сам курсор не трогает: сервис просто запускает Weston в kiosk-shell, а приложение идёт fullscreen через autolaunch. В weston.ini не было scale/transform/output-настроек, которые могли бы объяснить расхождение координат. В исходниках Weston 15.0.1 я посмотрел путь работы курсора: видимый курсор может рисоваться через отдельную KMS cursor plane, а клики при этом идут по libinput/Wayland координатам. Это хорошо объясняло форму бага: реальная позиция доходит до низа, а видимая стрелка застревает выше. Проверял и встроенный debug-путь Weston: Ctrl+Shift+Space, затем C, который должен переключать cursor planes. Но в реальном тесте это ничего не изменило. Поэтому стало ясно, что одной версии про hardware cursor plane недостаточно. Важная новая улика появилась, когда я проверил VM с наложением host cursor и guest cursor: как только я начинал печатать, сдвигался именно гостевой видимый курсор. То есть реальная мышь и координаты ввода не «уезжали»; ломался cursor surface внутри гостевой Wayland-сессии. Дальше я стал проверять GTK-слой. Страница «Имя пользователя» в ReadySet использует обычные поля ввода на базе Adw.EntryRow/GtkText. На каждый ввод там ещё меняется состояние страницы: проверка имени, CSS ошибки, avatar text, готовность кнопки «Дальше». Это могло усиливать гонку, но не выглядело корнем проблемы. Для сравнения я попробовал запустить отдельные приложения в той же Weston-сессии. weston-editor оказался плохим тестом: под kiosk-shell он падал с ошибкой про отсутствие text input manager. После этого я поставил GTK-приложение и запускал gtk4-widget-factory вместо ReadySet. gtk4-widget-factory воспроизвёл проблему в той же Weston-сессии. Это практически сняло подозрение с ReadySet как приложения: баг был в связке GTK4/Wayland/Weston. Чтобы понять точнее, я запускал gtk4-widget-factory через wrapper с WAYLAND_DEBUG=client. Сначала лог попал в приватный /tmp из-за PrivateTmp у systemd-сервиса, поэтому я перенёс вывод в /run/ready-set-service. В Wayland-логе нашёл ключевую последовательность. GTK один раз выставлял cursor surface через wl_pointer.set_cursor с hotspot 5,5, а потом переиспользовал тот же wl_surface и менял его через wl_surface.offset. При наведении на поле появлялся offset примерно -9,-10 и новый buffer. При первом нажатии клавиши GTK скрывал курсор через offset 14,15 и attach(nil). После движения мыши вниз GTK возвращал стрелку через offset примерно -5,-5 и новый buffer. Именно это вывело на конкретный баг Weston. В libweston/input.c обработчик pointer_cursor_surface_committed сначала проверял width == 0 и сразу выходил. Поэтому когда GTK делал cursor surface с attach(nil), Weston не применял new_origin к hotspot. Смещение терялось, а следующий cursor buffer применялся уже поверх старого hotspot. В итоге видимый курсор уезжал примерно на те самые 14-15 пикселей. Аналогичный путь есть и для tablet cursor. В будущем нужно будет рассмотреть патч Weston, который будет учитывать new_origin до выхода при width == 0. После этого я отдельно проверял, можно ли обойтись без патча Weston. Нормального штатного флага в Weston для этого не нашёл: use-pixman меняет renderer, но не гарантирует отключение проблемного пути, а WLR_NO_HARDWARE_CURSORS относится к wlroots, не к Weston. Запретить GTK скрывать курсор при печати тоже не получилось штатной настройкой. Поэтому практичным обходом стал запуск ReadySet через XWayland: XWayland скрывает курсор другим путём, через wl_pointer.set_cursor(NULL), и не попадает в проблемную комбинацию wl_surface.offset + attach(nil). Для этого в ready-set-service я включил xwayland=true в weston.ini, а в ready-set-start.sh перед запуском ReadySet выставил GDK_BACKEND=x11. В spec добавил зависимость xorg-xwayland и поднял release до 0.0.3-eter23. При первой live-проверке в VM сервис стал падать уже по другой причине: из-за PrivateTmp внутри namespace не было /tmp/.X11-unix, и XWayland не мог создать сокет. Поэтому в wrapper ready-set-service добавил создание /tmp/.X11-unix с правами 1777 перед запуском Weston. В установленной VM проверил runtime: ready-set-service был active, процесс XWayland работал, у ready-set были DISPLAY=:0 и GDK_BACKEND=x11. После этого дошёл до шага «Имя пользователя», ввёл тестовый текст и резко увёл мышь вниз. На XWayland курсор доходил до нижней кромки, прежний скос визуально не проявился. После сборки ISO и установки системы я проверил ReadySet в разных условиях: на одном мониторе и в конфигурации с несколькими мониторами. В проблемном сценарии с полем «Имя пользователя» через XWayland курсор больше не смещался. Отдельно я разобрался с автоматизацией VM, потому что такие проверки вручную отнимают слишком много времени: нужно подключать ISO, ловить boot menu, проходить установщик, делать снимки, проверять экран и иногда повторять одно и то же на разных видеорежимах. Сначала изучал Proxmox API. Сам по себе API позволяет читать конфиг и статус VM, управлять питанием, менять часть параметров, работать со снимками, ISO и boot order. Для полноценной работы с экраном нужен не только VM.Audit/VM.PowerMgmt, но и VM.Console: через vncproxy и vncwebsocket можно получить framebuffer, отправлять клавиши и события мыши. На VM 942 на gefest проверил этот путь практически. Токен был сохранён отдельно в /home/katze/.config/codex-secrets/proxmox.env. Сначала token валидировался, но VM 942 не была видна из-за прав и privilege separation. После настройки доступа к /vms/942 API стал возвращать status/config, а vncproxy начал отвечать 200. Дальше я смог запустить VM через API, подключиться к VNC websocket, пройти RFB handshake и снять PNG-скриншот с framebuffer. После этого проверил ввод: набрал текст в login prompt и очистил строку backspace-ами. Затем дорабатывал управление клавиатурой и мышью. Выяснилось, что для мыши важен USB tablet/absolute pointer, иначе координаты через VNC начинают вести себя как относительная PS/2-мышь и клики промахиваются. Кроме того, простой короткий RFB-handshake для мыши работал ненадёжно: пришлось делать полноценную инициализацию RFB-клиента, задавать pixel format/encoding, запрашивать framebuffer update и уже после этого отправлять pointer events. После этого заработали клики по координатам, правый и средний клик, настоящий double-click в одном VNC-сеансе и drag-and-drop. Для клавиатуры тоже появились нюансы. Обычные буквы, F1-F12, стрелки, модификаторы, NumPad, Esc, PageUp/PageDown и похожие клавиши удалось отправлять через RFB. Но PrtSc и Super/Win перехватываются GNOME Shell, а media/Fn-клавиши через этот путь нормально не подтвердились. Отдельно исправлял ввод символов: shifted-символы вроде _, @ и > нельзя было слать наивно как один символ, их нужно было вводить как физические US-клавиши с Shift. После этого проверял ввод на странице Key-Test и отдельным тестом с символами. На основе этих проверок сгенерировал отдельный инструмент /home/katze/Projects/proxmox-vnc-control с CLI pvevmctl. Он умеет делать screenshot, серию screenshot через watch, отправлять клавиши и текст, нажимать комбинации, удерживать клавишу, выполнять left/right/middle click, double-click, drag-and-drop и запускать сценарии с таймлайном. Это важно для будущей автоматизации: можно описывать не только одиночные клики, но и последовательность действий с параллельными задержками, снимками и проверкой результата. Скриншоты складываются в отдельный audit-каталог и не удаляются, чтобы потом можно было понять, что реально происходило на экране. Отдельно проверял multi-display в Proxmox. qxl2,memory=64 применился после полного stop/start QEMU, но через noVNC/GNOME второй монитор не появился: VNC framebuffer остался одним экраном. По документации Proxmox QXL завязан на SPICE, а для Linux-гостей дополнительные displays не обязаны появляться сами. Вариант с virtio-vga,max_outputs=2 выглядит правильнее без SPICE, но обычный API-токен не может задавать root-only custom args, а схема vga не принимает max_outputs. Поэтому Proxmox/noVNC оказался хорош для скриншотов, ввода и boot/installer-автоматизации, но не стал хорошей опорой для проверки нескольких мониторов. Для локальной VirtualBox VM я отдельно разобрал более подходящий путь автоматизации через ssh laptop. Через VBoxManage можно смотреть состояние VM, менять boot order, подключать и отключать ISO, делать screenshotpng, отправлять keyboardputstring/keyboardputfile/keyboardputscancode и управлять жизненным циклом VM. Для мыши лучше использовать отдельный helper vboxctl, который работает через VirtualBox API и отправляет абсолютные mouse events внутрь гостя, не двигая курсор хоста. Скриншоты сохраняются в /home/katze_942/VirtualBox VMs/screenshots, а затем их можно забирать локально и смотреть перед выбором координат. На VirtualBox особенно пригодились несколько практических ограничений. Для смены monitorcount, graphicscontroller и vram VM должна быть poweroff; saved-состояние не подходит, поэтому иногда приходится загружаться из состояния snapshot без сохранённой RAM. screenshotpng умеет снимать разные display index, но не показывает cursor overlay, поэтому видимый курсор нельзя доказать одним кадром. Также после отката нужно проверять все storage attachments, чтобы VM действительно загрузилась с установленного диска, а не снова с ISO. Итог: корневая причина смещения курсора найдена на уровне GTK4 cursor surface и обработки hotspot в Weston при wl_surface.offset + attach(nil). Для полноценного исправления нужен патч Weston, но для ReadySet сейчас выбран рабочий обход через XWayland. В текущей конфигурации Weston включает xwayland=true, ReadySet запускается с GDK_BACKEND=x11, перед стартом Weston создаётся /tmp/.X11-unix, а пакет ready-set-service тянет xorg-xwayland. Проверка на установленной VM показала, что проблемный сценарий на странице «Имя пользователя» через XWayland больше не воспроизводится.