Вступление
Всем доброго дня! В одной из предыдущих статей Вайбкодинг с нейросетью 1: проверяю сборку Flutter-приложения в AppImage:
- Сравнил Flutter и Kivy;
- Написал Hello World на Dart для Flutter;
- Использовал нейросети Perplexity и Giga Chat в качестве инструментов вайб-кодинга;
- Собрал приложение для Linux в AppImage.
Предыдущая статья была написана для конкурса «Властелин алгоритмов: сезон „ИИ в разработке“ на Хабре», где основной темой было применение ИИ в разработке. Это был мой первый опыт написания статьи про вайб-кодинг, и мне не очень зашла данная тематика, так как я не знаю Flutter. Ориентироваться только на то, что пишет нейросеть, довольно тяжело, потому что непонятно, к какому результату это может привести и насколько этот результат будет хорошим.
Статья была отражением того факта, что я начал сомневаться в стеке Kivy + Python. Когда я писал на Kivy + KivyMD 2.0.0 и собирал приложение под Linux, я сталкивался с рядом проблем, о которых речь пойдёт ниже в данной статье.
Что же касается Flutter и Dart, то мне понравились удобство и скорость приложения, которые они предоставляют. Однако этот стек нужно учить с нуля. Это несколько затрудняет переписывание приложения Kawai-Focus прямо сейчас, а на освоение Dart и Flutter может уйти много времени. Поэтому я решил попробовать перевести проект на более знакомый мне стек, а Dart и Flutter оставить запасным вариантом и не бросаться в них прямо сейчас.
В данной статье я попытаюсь переписать часть приложения Kawai-Focus на стек: FastAPI + Vue.js + Tauri + Ionic. Также я соберу приложение только с экраном «Таймеры» под Linux в AppImage, которое подойдёт для большинства Linux-дистрибутивов. Реализация одного экрана не будет слишком долгим процессом, а финальная сборка приложения позволит оценить сложность и удобство полного цикла разработки.
Данная статья не будет подробным гайдом, так как размер переписанного кода и его описание слишком велики для одной статьи. Однако я покажу часть кода проекта и расскажу о выбранном стеке, а также о трудностях и преимуществах, с которыми я столкнулся в процессе переписывания проекта. Я буду использовать часть кода старого проекта, который не касается Kivy и KivyMD, поэтому его душа не умрёт, а обновится.
Заваривайте чай, доставайте вкусняшки — пора «старый стек превращать в удобрение для новых помидор»! 🍅
Почему мне не подошёл Kivy?
Фреймворк Kivy я впервые увидел несколько лет назад в видеоролике одного блогера. Он рассказывал, как написать на нём простую игру «пинг-понг» и собрать её под Android с помощью Buildozer. Мне это показалось очень занятным, и я вручную переписал его гайд в коде, а затем собрал приложение под Android. Когда я запустил игру на телефоне, для меня это было чем-то волшебным, и мне это очень понравилось. Тогда у меня и возникла мысль попробовать сделать на нём что-то своё. Так, пару лет спустя, и появился прототип приложения Kawai-Focus.
Мне удалось реализовать цепочку таймеров и сохранение пользовательских таймеров в базу данных sqlite3. Однако стандартный дизайн Kivy был квадратным и топорным, и его явно нужно было менять. Каким-то чудом я нашёл форк KivyMD 2.0.0, который не заброшен и предоставляет современный Material Design. Я переписал дизайн, и он понравился пользователям в комментариях к статье. Мне казалось, что я почти справился, и я сел за сборку приложения под Linux.
Я ждал чуда, но вместо него получил разрушающее стек озарение…
На самом деле ещё до события, которое поставило на стеке Kivy крест, уже были тревожные звоночки:
- Лаги при работе с интерфейсом — особенно это чувствовалось при разворачивании информации о таймере на экране «Таймеры». Блок раскрывается с такими фризами, что это уже стыдно показывать пользователю. Работать с таким интерфейсом неприятно, и, скорее всего, человек просто не захочет пользоваться приложением;
- Вёрстка экранов — этот процесс оказался куда более муторным и непонятным, чем вёрстка в HTML. Логи часто сообщают о том, что используемый способ уже deprecated. Можно легко наткнуться на нерабочие варианты позиционирования элементов и другие странности;
- Звук, ломающий запуск приложения — мне пришлось перейти с
audio_sdl2наffpyplayer, так какaudio_sdl2вызывал падение приложения на этапе инициализации. При запуске Kivy не удавалось корректно инициализировать SDL-аудиодрайвер (отсутствие или конфликт системных аудиобиблиотек), из-за чего приложение завершалось ещё до отображения интерфейса; - Работа с версией KivyMD — разработка ведётся с форком, где рекомендуется либо клонировать репозиторий с главной веткой, либо использовать ссылку на
zip-архив этой ветки. Это крайне неудобно в работе, например, с Poetry, поскольку зависимость невозможно зафиксировать как версионированный пакет: отсутствуют релизы и теги, а значит, нельзя гарантировать воспроизводимую сборку и стабильность окружения.
Окончательно добил проект на Kivy процесс сборки приложения под Linux. Приложение требует наличия древней и устаревшей библиотеки для сенсорных экранов внутри Flatpak-пакета. Всё это в итоге сильно увеличивает размер сборки.
Вдобавок ко всему этому Kivy заметно устарел, и на нём пишет всё меньше людей. В результате получился бы очень специфичный, медленный, тяжёлый и нишевый продукт, которому скорее место в моих статьях — в качестве музейного экспоната и прототипа, а не в роли качественного и серьёзного приложения.
Новый стек
Наступил тот момент, когда Kawai-Focus должен перейти на новый стек. Что же это может быть? Как я писал во вступлении к этой статье, я уже успел попробовать Flutter + Dart и пришёл к выводу, что это хороший стек, но переход на него займёт слишком много времени. Некий Сергей уже вовсю ждёт возможности опробовать мой Kawai-Focus, поэтому уход в кардинально новый стек — это заставить его ждать условный месяц. Да и сам я хочу получить результат побыстрее, поэтому начал подыскивать более удобные альтернативы на Python.
Одной из таких альтернатив мог оказаться фреймворк Flet. Казалось бы, хороший вариант: написан на Python, использует Flutter под капотом, позволяет писать код быстрее и проще, да и устаревших библиотек не требует. Однако верстать внешний вид на Python оказалось бы крайне неудобно. В качестве прототипа он подошёл бы хорошо, но довести интерфейс до уровня полноценного продукта было бы очень непросто.
В итоге я решил взять на пробу универсальный стек, который больше подойдёт веб-разработчику, решившему попробовать себя в кроссплатформенной разработке, не теряя при этом своей «веб-идентичности».
На самом деле изначально я обучался именно на веб-разработчика с использованием Python, а использование Kivy немного увело меня в сторону от этого направления. Работать сразу в двух стэках — это серьёзное распыление внимания в профессиональной карьере. Поэтому я решил сократить это распыление и немного подкорректировать используемый стек.
Основной стек:
- FastAPI — вся логика приложения и бэкенд;
- Vue.js — отвечает за внешний вид (фронтенд);
- Ionic — нативный UI и мобильная версия;
- Tauri — движок для десктопной версии приложения.
Остановимся немного на каждой технологии.
FastApi
FastAPI — современный асинхронный Python-фреймворк для создания API, ориентированный на высокую производительность, строгую типизацию и удобство разработки. В десктопном приложении FastAPI обычно выступает в роли локального бэкенда, обрабатывающего бизнес-логику, работу с данными и взаимодействие с внешними сервисами.
Благодаря ASGI, асинхронности и использованию Starlette и Pydantic, FastAPI показывает высокую скорость как в веб-приложениях, так и в десктопных сценариях, где фронтенд общается с ним через HTTP или WebSocket. При этом асинхронный подход больше подходит для веб-приложений с большим количеством пользователей и запросов. Для локального приложения с одним пользователем и базой данных sqlite3 вполне достаточно обычного синхронного подхода. Однако я не исключаю, что в будущем могу реализовать более сложную веб-версию, где асинхронность будет действительно необходима.
С этим фреймворком я уже знаком, но опробовал далеко не все его возможности. В основном я использовал FastAPI для работы с WebSocket и раздачи HTML. Это для меня комфортнее, чем Dart, так как здесь у меня есть пусть и небольшой, но реальный опыт, который хочется расширить и углубить.
Vue.js
Vue.js — прогрессивный JavaScript-фреймворк для создания пользовательских интерфейсов с упором на реактивность и компонентный подход. В десктопном приложении Vue.js отвечает за внешний вид и пользовательское взаимодействие, выступая фронтендом, работающим поверх веб-движка (например, Tauri). Благодаря лёгкости, виртуальному DOM и эффективной реактивной системе Vue.js обеспечивает высокую отзывчивость интерфейса как в вебе, так и в десктопных приложениях.
С этим фреймворком я знаком слабо, но у меня был небольшой опыт работы с ним примерно два года назад. Честно говоря, у меня больше практики с React и обычным JavaScript. Vue.js придётся освежить в памяти, но так как с фронтендом я всё же сталкивался, процесс пойдёт заметно быстрее, чем изучение Dart и Flutter с нуля.
Ionic
Ionic — фреймворк для создания кроссплатформенных приложений с использованием веб-технологий (HTML, CSS и JavaScript). В десктопных и мобильных приложениях Ionic отвечает за нативный внешний вид интерфейса и доступ к возможностям устройства, работая поверх фронтенда (например, Vue.js). За счёт оптимизированных UI-компонентов и интеграции с нативными API Ionic обеспечивает приемлемую производительность как на мобильных устройствах, так и в десктопных оболочках.
Как раз раньше я пробовал проходить гайды по Ionic в паре с Vue.js, что внушает надежду на более быстрое освоение этого фреймворка по сравнению с Flutter.
Tauri
Tauri — фреймворк для создания кроссплатформенных десктоп-приложений на базе веб-технологий. В десктопном приложении Tauri выступает в роли оболочки и движка, а его бэкенд написан на Rust, что даёт высокую производительность, безопасность памяти и существенно меньший размер сборки по сравнению с Electron-подобными решениями. Благодаря тесной интеграции с нативной системой и минимальному оверхеду Tauri обеспечивает быструю работу интерфейса и хорошую отзывчивость на всех поддерживаемых платформах.
Схема десктоп-приложения на Tauri:
Vue
↓
Tauri
↓
System WebView
↓
Native Window- Vue — отвечает за пользовательский интерфейс и логику фронтенда, формируя HTML, CSS и JavaScript-приложение;
- Tauri — связующий слой между веб-интерфейсом и операционной системой, управляющий окнами, событиями и безопасным доступом к нативным возможностям;
- System WebView — системный веб-движок, в котором отрисовывается Vue-приложение без встроенного браузера;
- Native Window — нативное окно операционной системы, с которым взаимодействует пользователь.
Tauri позволяет работать с системными функциями напрямую из приложения:
- Файловая система — чтение, запись и управление файлами с контролируемыми правами доступа;
- Автозапуск — запуск приложения вместе с системой;
- Системный трей — иконка и управление из области уведомлений;
- Нотификации — показ системных уведомлений;
- Системные хоткеи — регистрация глобальных сочетаний клавиш.
Tauri изначально спроектирован с упором на строгую безопасность: доступ к нативным API ограничен, разрешения настраиваются явно, а фронтенд изолирован от системы, что существенно снижает риск уязвимостей.
Всё это звучит хорошо, но раз речь идёт о веб-приложении, возникает закономерный вопрос: а как обстоят дела с потреблением оперативной памяти и размером итоговой сборки?
Одно из ключевых преимуществ Tauri — минимальный размер приложения и низкое потребление ресурсов. Так как Tauri использует системный WebView, а не встраивает собственный браузер, размер сборки обычно составляет несколько мегабайт, а не сотни, как у Electron-подобных решений. За счёт бэкенда на Rust и отсутствия тяжёлого runtime Tauri потребляет значительно меньше оперативной памяти, что особенно важно для фоновых утилит и постоянно работающих приложений.
Дополнительно я планирую скомпилировать Python-код с помощью Nuitka в C-код — это позволит ускорить выполнение и уменьшить занимаемый объём по сравнению с обычным Python.
В итоге получился довольно интересный стек как для веб-, так и для десктоп-разработки, который мне уже не терпится опробовать в деле. Кроме того, если убрать Tauri, можно сделать веб- и мобильную версии приложения, практически не меняя код. Звучит многообещающе, но как это покажет себя в реальном проекте — узнаете в следующем разделе статьи.
Процесс переезда
Теперь, когда обозначен новый стек приложения, пора заняться процессом «переезда» Kawai-Focus на него. Так совпало, что этот процесс происходит перед Новым годом, и получается, что вместе с обновлением года и календаря обновится и код приложения.
Лично я очень жду новогоднего чуда и хочу, чтобы моё приложение в новом году стало ещё лучше, а у читателей сбылись их надежды и мечты в наступающем 2026 году ⛄
Основа проекта
Часть кода из приложения Kawai-Focus я перенесу в новый проект, улучшая его в процессе переноса. Идея заключается в том, чтобы брать старый код и по возможности адаптировать и улучшать его под новый стек. Таким образом я сохраню основную идею приложения, но при этом сразу сделаю его лучше.
Например, CRUD-операции для работы с базой данных будут отлично работать и на новом стеке. В старом коде уже есть решения, которые вполне можно доработать и улучшить. О процессе рефакторинга я расскажу в следующих статьях, а сейчас сосредоточусь именно на описании переезда и общих идеях проекта.
Я решил не трогать старый репозиторий, а создать новый. В старом репозитории я укажу, что проект переехал на новый стек, и добавлю ссылку на него. Таким образом код старого проекта останется на память и будет использоваться в качестве примеров для статей на сайте «Код на салфетке».

Также изменениям подвергнется структура проекта, менеджер зависимостей (замена poetry на uv), а ещё будет добавлен линтер ruff, работающий под управлением pre-commit. По этой теме недавно у Ивана proDreams вышла отличная статья «ИИ бот-модератор 1: Начало проекта», где подробно рассказывается, как настроить uv и ruff под управлением pre-commit. Поэтому, в целях экономии времени, я воспользуюсь этим материалом для настройки базовой инфраструктуры проекта.
Менеджер пакетов uv написан на Rust и работает быстрее poetry, а ruff — это линтер, который проверяет код, тогда как pre-commit — фреймворк, автоматически запускающий набор проверок. Часто я сосредотачиваюсь на создании прототипа, игнорируя современные инструменты разработки. Поэтому в новом 2026 году я хочу использовать актуальные инструменты и сделать стек более свежим.
Теперь я займусь структурой проекта. Ниже я покажу примерную структуру, которая выступает в роли наброска первоначальной идеи. Итоговый вариант, скорее всего, будет отличаться от этой заготовки — ровно так же, как набросок художника отличается от готовой картины.
Базовая структура проекта:
kawai-focus-v2/
├── src/ # src-layout от uv (ВАЖНО)
│ └── kawai_focus_v2/ # python package
│ ├── database/ # всё, связанное с БД
│ │ ├── __init__.py
│ │ ├── db.py # engine, SessionLocal
│ │ ├── models.py # Base + импорт моделей
│ │ └── mixins.py
│ │
│ ├── tests/
│ │
│ ├── alembic/ # Alembic как часть приложения
│ │ ├── __init__.py
│ │ ├── README.md
│ │ ├── env.py
│ │ ├── script.py.mako
│ │ └── versions/
│ │
│ ├── __init__.py
│ ├── main.py # FastAPI entrypoint
│ ├── api/ # routers
│ ├── core/ # config, settings, paths
│ │ ├── config.py
│ │ └── settings.py
│ ├── schemas/ # Pydantic схемы
│ └── services/ # бизнес-логика
│
├── client/ # Ionic + Vue (frontend)
│ ├── src/
│ │ ├── main.js
│ │ ├── App.vue
│ │ ├── theme/
│ │ ├── views/
│ │ ├── services/
│ │ │ └── api.js
│ │ └── router/
│ │ └── index.js
│ │
│ ├── tests/
│ ├── public/
│ ├── capacitor.config.json
│ ├── ionic.config.json
│ ├── package.json
│ └── vite.config.js
│
├── desktop/ # Tauri
│ └── src-tauri/
│ ├── tauri.conf.json
│ └── src/
│ └── main.rs
│
├── .gitignore
├── .pre-commit-config.yaml
├── alembic.ini
├── .python-version
├── pyproject.toml
├── uv.lock
├── README.md
└── LICENSEОписание основных элементов структуры:
src/kawai_focus_v2/— основной Python-пакет приложения, оформленный поsrc-layout, в котором размещены бизнес-логика, API и инфраструктурный код;client/— фронтенд-приложение на Ionic + Vue.js, отвечающее за пользовательский интерфейс и взаимодействие с бекендом;desktop/— конфигурация и исходный код десктопной оболочки на Tauri, связывающей фронтенд с нативным окружением ОС;.pre-commit-config.yaml— конфигурация хуков pre-commit для автоматической проверки кода перед коммитами;.python-version— файл с указанием версии Python, используемой в проекте, для синхронизации окружений разработки;pyproject.toml— основной файл конфигурации проекта, описывающий зависимости, метаданные и настройки инструментов Python;uv.lock— lock-файл менеджера зависимостейuv, фиксирующий точные версии пакетов для воспроизводимых сборок;LICENSE— файл с лицензией проекта, определяющий условия использования и распространения кода.
Я использую следующие команды для начала работы в проекте:
git clone git@github.com:Arduinum/kawai-focus-v2.git— клонирует репозиторий проекта из GitHub в локальную рабочую директорию;- Установка uv (любым удобным способом);
uv init --package kawai-focus-v2— инициализирует Python-проект с использованиемuv, создавая базовую структуру и конфигурационные файлы;cd kawai-focus-v2— переходит в директорию инициализированного проекта;uv add --dev pre-commit— добавляетpre-commitв dev-зависимости проекта для автоматической проверки кода;uv run pre-commit install— устанавливает Git-хуки pre-commit, чтобы проверки запускались автоматически перед каждым коммитом.
После инициализации проекта у меня получилась следующая структура.

Мой пример дополняет статью Ивана тем, что в моём случае репозиторий и такие файлы, как README.md, .gitignore и LICENSE, создаются в GitHub, а не на локальном ПК. Приятный момент заключается в том, что команда uv init --package kawai-focus-v2, выполненная рядом с каталогом репозитория, а не внутри него, не пересоздаёт и не перезатирает содержимое README.md и .gitignore.
Далее я проверяю работу pre-commit, выполнив следующую команду. Я впервые использую pre-commit, поэтому было особенно интересно посмотреть, какой результат она выдаст.
uv run pre-commit run --allДанная команда запускает все настроенные pre-commit хуки вручную для всех файлов проекта, а не только для тех, которые были изменены или добавлены в индекс Git.
Вывод команды:
trim trailing whitespace.................................................Failed
- hook id: trailing-whitespace
- exit code: 1
- files were modified by this hook
Fixing .gitignore
check yaml...........................................(no files to check)Skipped
check for case conflicts.................................................Passed
check for merge conflicts................................................Passed
fix end of files.........................................................Passed
pyupgrade............................................(no files to check)Skipped
ruff check...........................................(no files to check)Skipped
ty check.................................................................PassedПриятно видеть, что инструмент сразу начал приносить пользу. Хук trim trailing whitespace нашёл лишние пробелы и удалил их, о чём говорит сообщение files were modified by this hook.
После этого я добавляю все файлы из корня проекта и делаю первый коммит.
git add . && git commit -m "feat: init project"Вывод команды:
trim trailing whitespace.................................................Passed
check yaml...............................................................Passed
check for case conflicts.................................................Passed
check for merge conflicts................................................Passed
fix end of files.........................................................Passed
pyupgrade................................................................Passed
ruff check...............................................................Passed
ty check.................................................................Passed
[master 4608b1a] feat: init project
7 files changed, 214 insertions(+), 2 deletions(-)
create mode 100644 .pre-commit-config.yaml
create mode 100644 .python-version
create mode 100644 .vscode/settings.json
create mode 100644 pyproject.toml
create mode 100644 src/kawai_focus_v2/__init__.py
create mode 100644 uv.lockВсе хуки отработали корректно, о чём свидетельствует статус Passed у каждого из них, и коммит успешно применился.
Чуть позже выяснилось, что я использую более старую версию ty-pre-commit, поэтому я выполнил команду обновления. Заодно обновился и ruff-pre-commit, который тоже был устаревшим.
pre-commit autoupdateВывод команды:
[https://github.com/pre-commit/pre-commit-hooks] already up to date!
[https://github.com/asottile/pyupgrade] already up to date!
[https://github.com/astral-sh/ruff-pre-commit] updating v0.13.0 -> v0.14.10
[https://foundry.fsky.io/vel/ty-pre-commit] updating 0.0.3 -> 0.0.8Бекенд
Обновлять и писать новый код я начну с бекенда так как основная логика приложения у меня будет и именно там. Начну с установки dev зависимостей для python.
uv add --dev pytest ruffpytest— библиотека для тестов;ruff— линтер для проверки кода на ошибки (его использует alembic у меня в проекте).
Я откажусь от библиотеки ffpyplayer для воспроизведения звука, так как она может вызывать проблемы при установке на Windows и в целом больше ориентирована на Linux. В данной ситуации самым простым и надёжным решением будет использование фронтенда для воспроизведения звука — через Web Audio API или HTML5 Audio. Это позволит одинаково просто воспроизводить звук на разных платформах, не задумываясь о поддержке и установке аудиобиблиотек под различные операционные системы.
Установка основных зависимостей:
uv add fastapi sqlalchemy pydantic-settings alembic uvicorn- FastAPI — веб-фреймворк для создания API и бэкенд-логики приложения;
- SQLAlchemy — ORM и инструментарий для работы с базой данных и SQL;
- pydantic-settings — управление конфигурацией приложения через настройки и переменные окружения;
- Alembic — инструмент для управления миграциями схемы базы данных;
- Uvicorn — ASGI-сервер для запуска FastAPI-приложения.
Инициализация alembic:
uv run alembic init src/kawai_focus_v2/database/alembicНастройки alembic я так же взял из старого проекта и поменял лишь импорты.
Я понимаю, что FastAPI хорош для асинхронной работы, но у меня десктоп-приложение с SQLite3, который не поддерживает асинхронность. Если я напишу веб-версию в будущем, которой потребуется асинхронность, то скорее всего создам её в отдельном репозитории ибо так будет гораздо удобнее.
Файлы models.py и mixins.py я просто скопирую из старого проекта и поменяю пути импорта если это будет необходимо. Файл settings.py теперь будет лежать в отдельной папке (core/) — это признак явного разделения инфраструктуры и бизнес-кода: настройки становятся частью слоя конфигурации, а не «глобальной помойкой» проекта.
Создание миграции для таблицы Timer в бд:
uv run alembic revision --autogenerate -m "Create Timer"Вывод команды:
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added table 'timer'
Generating /media/user_name/Files2/Programming/Python/Projects/kawai-focus-v2/src/kawai_focus_v2/database/alembic/versions/2025_12_25_1504-503b50886979_create_timer.py ... done
Running post write hook 'ruff' ...
1 file reformatted
doneКстати я заметил, что у меня теперь два ruff один в pre-commit используется, а другой в dev зависимостях и настроен для alembic и миграций. Есть два варинта: оставить как есть два ruffa или же оставить один ruff и дёргать его из .venv окружения c pre-commit для проверки перед коммитом. Для этого нужно будет поменять конфиг .pre-commit-config.yaml.
Напишите в комметариях какой вариант грамотнее на ваш взгляд. Мне будет очень интересно узнать ваше мнение. Я пока оставлю оба варианта и дождусь ваших ответов.
В .env поменяю название бд на NAME_DB = "timer.sqlite3" так будет более явнее чем timer.db.
Применение миграций:
uv run alembic upgrade headВывод команды:
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Running upgrade -> 503b50886979, Create TimerОтлично у меня теперь есть база данных с полями, с которой уже можно работать. В следующей части статьи я расскажу более подробно о самом коде бекенда, а пока я перейду к процессу создания бинарника.
Компиляция в бинарный файл
Nuitka — это компилятор для Python, который превращает Python-код в нативный исполняемый файл, компилируя его через C и убирая зависимость от интерпретатора на целевой машине.
Бинарник — это готовый исполняемый файл в машинном коде (например, ELF в Linux), который запускается напрямую операционной системой без shell-скриптов и интерпретаторов. Он ускоряет работу Python-кода, потому что компилирует его в машинный код, устраняя интерпретацию на лету и уменьшая накладные расходы на запуск и импорт модулей.
Процесс компиляции будет происходить на операционной системе Linux Debian!
Установка зависимостей для компиляции:
uv add --dev nuitka patchelfДля переиспользования кэша при компиляции можно установить следующую библиотеку.
sudo apt install ccacheccache при работе с Nuitka кэширует результат компиляции C/C++ файлов, которые Nuitka генерирует из Python-кода, чтобы при повторной сборке те же исходники не компилировались заново.
Теперь можно запустить сам процесс компиляции.
uv run nuitka \
--standalone \
--follow-imports \
--include-module=uvicorn \
--include-package=fastapi \
--include-package=sqlalchemy \
--output-filename=backend-x86_64-unknown-linux-gnu.bin \
./src/kawai_focus_v2/main.pyРазбор команды:
uv run nuitka— запускает Nuitka через твой CLI (uv), чтобы скомпилировать Python-код;--standalone— собирает полностью независимый бинарник со всеми зависимостями, не требующий установленного Python;--follow-imports— включает в сборку все импортированные модули автоматически;--include-module=uvicorn— явно добавляет модульuvicornв бинарник, даже если он используется динамически;--include-package=fastapi— включает пакетfastapiцеликом в сборку;--include-package=sqlalchemy— включает пакетsqlalchemyцеликом в сборку;--output-filename=backend-x86_64-unknown-linux-gnu.bin— задаёт имя и путь для сгенерированного бинарника;./src/kawai_focus_v2/main.py— указывает основной Python-файл, который будет точкой входа для компиляции.
В конце компиляции появится примерно следующая информация.
Nuitka: Successfully created 'main.dist/backend-x86_64-unknown-linux-gnu.bin'.Строка говорит о том, что бинарный файл успешно создался. Протестировать работу бинарника можно, запустив его из терминала.
./main.dist/backend-x86_64-unknown-linux-gnu.binВывод команды:
INFO: Started server process [151235]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8090 (Press CTRL+C to quit)
INFO: 127.0.0.1:32958 - "GET /docs HTTP/1.1" 200 OK
INFO: 127.0.0.1:32958 - "GET /openapi.json HTTP/1.1" 200 OKСудя по логам, Uvicorn успешно поднял бекенд на FastAPI по локальному адресу http://127.0.0.1:8090.
Фронтенд
После того как я успешно создал, настроил и протестировал бинарник бекенда, я перехожу к фронтенду.
Установка npm:
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -— добавляет репозиторий Node.js 24.x и настраивает его для установки через apt;sudo apt install -y nodejs— устанавливает Node.js и npm из настроенного репозитория;node -v— проверяет версию установленного Node.js;v24.12.0— пример вывода командыnode -v;npm -v— проверяет версию установленного npm;11.6.2— пример вывода командыnpm -v;cd client— переходит в директорию фронтенда проекта;npm run build— собирает фронтенд-проект в production-режиме, создавая готовые к раздаче файлы.
Установка ionic:
sudo npm install -g @ionic/cli— устанавливает глобально CLI фреймворка Ionic;ionic --version— проверяет установленную версию Ionic;7.2.1— пример вывода командыionic --version;ionic serve— запускает локальный dev-сервер для проекта Ionic и открывает его в браузере.
Я написал код для фронтенда, который получает от бекенда список таймеров. Так как описание этого кода не помещается в текущую статью, я перенесу рассказ о нём на одну из следующих частей.
Мне понадобятся пару плагинов для взаимодействия tauri и фронтенда.
npm install @tauri-apps/plugin-shell @tauri-apps/apiРазбор команды:
@tauri-apps/plugin-shell— плагин для Tauri, который позволяет фронтенду запускать сторонние процессы и команды на локальной машине через безопасный API;@tauri-apps/api— набор официальных API Tauri для работы с файловой системой, окнами, уведомлениями, диалогами и другими возможностями нативного приложения.
Для проверки запускаю фронтенд через ionic.
ionic serveВывод команды:
> vite --host=localhost --port=8100
[vite] ➜ Local: http://localhost:8100/
[vite] ➜ press h + enter to show help
[INFO] Development server running!Фронтенд успешно поднялся на локальном адресе http://localhost:8100/.
Если в консоли браузера появляется ошибка, связанная с CORS, и данные не отображаются, это означает, что необходимо разрешить CORS на бекенде.

После того как я разрешил URL фронтенда в CORS бекенда, запрос успешно выполнился, а данные отобразились на фронтенде.

Пока единственное действие, которое можно выполнить на странице таймеров, — развернуть информацию о таймере. Интересный момент: если развернуть другой таймер, первый будет сворачиваться автоматически. Это позволяет сократить путь пользователя и не держать большое количество раскрытых таймеров одновременно.

Desktop на Tauri
Теперь я перейду к одной из самых интересных частей проекта — настройке desktop-версии приложения.
Установка зависимостей
sudo apt install -y \
build-essential \
curl \
wget \
file \
libssl-dev \
libgtk-3-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
pkg-config \
libwebkit2gtk-4.1-devРазбор зависимостей:
build-essential— устанавливает набор инструментов для компиляции C/C++ (gcc, g++, make и др.);curl— утилита для скачивания файлов и работы с URL через командную строку;wget— ещё одна утилита для загрузки файлов из интернета;file— определяет тип файла (например, бинарник, текстовый или скрипт);libssl-dev— библиотеки и заголовочные файлы OpenSSL для работы с криптографией и HTTPS;libgtk-3-dev— библиотеки и заголовки GTK3 для создания графического интерфейса;libayatana-appindicator3-dev— библиотеки для создания системных индикаторов/иконок в панели задач;librsvg2-dev— библиотеки для работы с SVG-файлами (отрисовка и конвертация в растровые изображения);pkg-config— утилита для получения информации о библиотечных файлах и их флагах компиляции;libwebkit2gtk-4.1-dev— библиотеки WebKitGTK для интеграции веб-контента в GTK-приложения.
Установка Rust:
Я не смог скачать установщик с официального сайта (курсор зависал и пакеты не скачивались), поэтому пришлось использовать зеркала:
export RUSTUP_DIST_SERVER=https://rsproxy.cn
export RUSTUP_UPDATE_ROOT=https://rsproxy.cn/rustupcurl --proto '=https' --tlsv1.2 -sSf https://rsproxy.cn/rustup-init.sh | sh— скачивает и запускает скрипт установки Rust через rustup;Rust is installed now. Great!— пример вывода скрипта, подтверждающий успешную установку Rust;source ~/.cargo/env— подгружает переменные окружения Rust в текущую сессию терминала;cargo --version— проверяет версию установленного пакетного менеджера и сборщика проектов Cargo;cargo 1.92.0 (344c4567c 2025-10-21)— пример вывода командыcargo --version;rustc --version— проверяет версию установленного компилятора Rust;rustc 1.92.0 (ded5c06cf 2025-12-08)— пример вывода командыrustc --version.
Установка Tauri:
cargo install tauri-cli— устанавливает CLI-инструменты Tauri через Cargo для работы с проектами Tauri;cargo tauri --version— проверяет установленную версию Tauri CLI;tauri-cli 2.9.6— пример вывода команды, показывающий текущую версию Tauri CLI.
Инициализация проекта:
cargo tauri initНеобходимо будет ответить на несколько вопросов в интерактивном режиме: имя приложения, URL сервера, команды сборки фронтенда и т.д.
✔ What is your app name? · Kawai-Focus
✔ What should the window title be? · Kawai-Focus
✔ Where are your web assets (HTML/CSS/JS) located, relative to the "<current dir>/src-tauri/tauri.conf.json" file that will be created? · ../client/dist
✔ What is the url of your dev server? · http://localhost:8100
✔ What is your frontend dev command? · cd ../client && ionic serve
✔ What is your frontend build command? · cd ../client && npm run buildРекомендую добавить команду, которая подключает плагин для запуска сторонних процессов и команд из Tauri-приложения.
cargo add tauri-plugin-shellВ данной статье я не поместил информацию о настройке конфигурации Tauri — об этом я обязательно расскажу в одной из следующих частей. Задача этой статьи — показать идею, установку, первичную настройку, запуск приложения и процесс сборки.
Поговорим о смене иконок приложения на свои. Приложение использует несколько размеров иконок, но позволяет из одного изображения (например, 512×512) сгенерировать все необходимые размеры автоматически.
Дефолтные icons:

Команда для создания иконок:
cargo tauri icon path_your_iconПосле выполнения команды в папке icons дефолтные иконки заменились на мои иконки с помидорками.

Запуск в режиме разработки:
cargo tauri devРежим разработчика удобен тем, что позволяет запускать консоль разработчика, похожую на консоль браузера: анализировать ошибки, смотреть HTML и многое другое. В данном случае консоль показала, что приложение не может найти таблицу taimer в базе данных.

Работоспособный экран приложения выглядит так:

Сборка под Linux в AppImage
Приступим к последнему этапу — сборке приложения AppImage под Linux. AppImage — это формат пакета для Linux, который позволяет распространять приложение вместе со всеми его зависимостями в одном исполняемом файле, который можно запускать напрямую, без установки и изменения системы. Такое приложение удобно запускать, оно не требует установки, поэтому я решил начать с него.
⚠️ Важно: сборка AppImage возможна только на Linux, и для неё должны быть установлены системные зависимости (libwebkit2gtk, libgtk-3, patchelf и др.).
Команда сборки:
cargo tauri buildВывод команды:
Finished 3 bundles at:
/media/user_name/Files2/Programming/Python/Projects/kawai-focus-v2/desktop/src-tauri/target/release/bundle/deb/Kawai-Focus_0.1.0_amd64.deb
/media/user_name/Files2/Programming/Python/Projects/kawai-focus-v2/desktop/src-tauri/target/release/bundle/rpm/Kawai-Focus-0.1.0-1.x86_64.rpm
/media/user_name/Files2/Programming/Python/Projects/kawai-focus-v2/desktop/src-tauri/target/release/bundle/appimage/Kawai-Focus_0.1.0_amd64.AppImageПоследние строки вывода показывают, что команда успешно собрала приложение сразу в трёх форматах: .deb, .rpm и .AppImage.
Запуск приложения
Для запуска приложения достаточно нажать на файл Kawai-Focus_0.1.0_amd64.AppImage, который находится по пути desktop/src-tauri/target/release/bundle/appimage.

Если запустить его из терминала, то в логи приложения, отображаемые Tauri, будет видно, что происходит в процессе. Для этого нужно написать специальный код на языке Rust. Подробнее об этом я обязательно расскажу в следующей части статьи. А пока что я успешно запустил приложение на Debian 12 с X11.

Мне очень понравилось, что хорошо работает адаптив, и в теории им будет удобно пользоваться даже на мобильных устройствах.

Также мне удалось успешно запустить приложение на своём ноутбуке с Debian 13 и Wayland.

Но не обошлось и без ложки дёгтя. Приложение не смогло отобразить окно в Arch Linux на ПК Сергея (того самого человека, который больше всего ждал моё приложение). Возможно, проблема связана с webkit2gtk, который предоставляет компонент для рендеринга веб-страниц внутри приложений, использующих GTK (графическую библиотеку).
Так же на ПК Ивана в Arch запустилось отображение, но возникла проблема с undefined symbol: rl_print_keybinding при запуске бекенда, которая связана с динамическими зависимостями, которые bash подхватывает внутри AppImage.
Будет очень интересно решить эти проблемы и запустить Kawai-Focus на Arch Linux. Такие ситуации только подстёгивают мой интерес к продолжению работы над приложением.
Анонс на следующие статьи
Сегодня я собрал первый урожай новых помидоров, которые вырастил, опираясь на предыдущий опыт выращивания 🙂
Я очень рад, что у меня получилось собрать первое работающее десктопное приложение под Linux. Однако не обошлось без подводных камней, поэтому в следующей статье я расскажу, с какими проблемами столкнулись мои товарищи при тестировании на Arch.
Также я постараюсь исправить критические проблемы запуска бэкенда на Arch и успешно запустить на нём своё приложение. Для этого, скорее всего, я уберу sh-скрипт и создам бинарник другим способом. Я пытался с помощью Nuitka собрать один файл бинарника, но Tauri, так сказать, немного испортил его в процессе сборки. Что с ним произошло, узнаете в следующей статье.
Кроме того, осталось много недосказанной информации по коду проекта, которая не поместилась в текущую статью. Например, я не показал код фронтенда, настройку конфигов для Tauri и многое другое. Всё это ждёт вас в следующих частях.
Код приложения я решил пока не выкладывать, так как ещё не показал его в статье. Я выложу сам AppImage, но запуск бэкенда внутри через sh-скрипт у вас может не сработать, как это произошло у Ивана под Arch!
Если у вас есть мысли о том, как можно улучшить проект, пишите в комментариях — с удовольствием ознакомлюсь с вашими предложениями!
Читайте продолжение — не пропустите!
Поздравления с НГ
Дорогие подписчики, гости канала и команда «Код на салфетке» — поздравляю всех с наступившим Новым 2026 годом и Рождеством! Желаю каждому, чтобы в 2026-м исполнились самые заветные мечты, а также чтобы год принёс рост, интересные задачи и успехи в IT-карьере.

Заключение
- Обсудили, почему Kivy не подошёл для моего проекта;
- Проект был переведён на новый стек технологий;
- Собрано приложение для Linux в формате AppImage.
Комментарии