BorisovAI

Блог

Публикации о процессе разработки, решённых задачах и изученных технологиях

Найдено 20 заметокСбросить фильтры
Исправлениеtrend-analisis

Когда унификация интерфейса оказывается архитектурной головоломкой

# Унификация — это неочевидно сложно Задача стояла простая на словах: «Давай выровняем интерфейс страниц тренда и анализа, чтобы не было разнобоя». Типичное дело конца спринта, когда дизайн требует консистентности, а код уже рассеялся по разным файлам с немного разными подходами. В проекте **trend-analisis** у нас две главные страницы: одна показывает тренды с оценками, другая — детальные аналитические отчёты. Обе они должны выглядеть как *части одного целого*, но на деле они разошлись. Я открыл `trend.$trendId.tsx` и `analyze.$jobId.report.tsx` и понял, что это как смотреть на двух братьев, которые выросли в разных городах. **Первым делом я разобрался с геометрией.** На мобильных устройствах кнопки на странице тренда вели себя странно — они прятались за правый край экрана, как непослушные дети. Перевёл их в стек на мобильных и горизонтальный ряд на десктопе. Простая история, но именно такие детали создают ощущение недоделанности. Потом пошло интереснее. **ScorePanel** — компонент с оценкой и её визуализацией — тоже требовал внимания. На странице тренда Sparkline (такие симпатичные маленькие графики) были отдельно от оценки, на странице анализа они находились где-то рядом. Решил переместить Sparkline внутрь ScorePanel, чтобы блок оценки стал полноценным, законченным элементом. **Но главный подвох ждал в бэкенде.** Когда я нырнул в `routes.py`, обнаружил, что оценка анализа считается в диапазоне 0–1 и потом нормализуется. Странная архитектура: пользователь видит на экране число 7–8, а в коде живёт 0.7–0.8. Когда возникла необходимость унифицировать, пришлось переделать — теперь всё работает в единой шкале 0–10 от фронтенда до бэкенда. Ещё одна муха в супе: переводы. Каждый отчёт имеет title и description. Вот только они часто приходили на разных языках — title на английском, description на русском, потому что система переводов разрасталась бессистемно. Пришлось переделать архитектуру на `get_cached_translations_batch()`, чтобы title и description синхронизировались по локали. Вот тут и проявляется одна из *типичных ловушек разработки*: когда система растёт, легко получить состояние, при котором разные части кода решают одну и ту же задачу по-разному. Кэширование переводов, кэширование данных, нормализация чисел — каждая из этих проблем порождает своё микрорешение, и вскоре у вас сложная паутина зависимостей. Решение: честный код-ревью и документирование паттернов, чтобы новичок не добавил пятый способ кэширования. **В итоге:** две страницы теперь выглядят как надо, API вернулся к нормальным оценкам (7–8 вместо 1), переводы синхронизированы. Git commit отправлен, бэкенд запущен на порту 8000. Дальше в плане новые исправления — благо материал есть. Чему научился: унификация — не просто про UI, это про согласованность логики по всему стеку. Порой проще переделать целый компонент, чем мучиться с костылями. 😄 Почему backend разработчик плюёт на фронтенд? Потому что он работает в консоли и ему всё равно, как это выглядит.

#claude#ai#python#git#api
Разработка: Trend Analisis
10 февр. 2026 г.
Исправление

Когда GitLab Runner нашел 5 ошибок TypeScript за 9 секунд

# GitLab Runner сломал сборку: как мы спасали TypeScript проект Понедельник, 10 февраля. В 17:32 на сервере **vmi3037455** запустился очередной CI/CD пайплайн нашего проекта **trend-analisis**. GitLab Runner 18.8.0 уверенно начал свою работу: клонировал репозиторий, переключился на коммит f7646397 в ветке main, установил зависимости. Всё шло как надо, пока... Сначала казалось, что всё в порядке. `npm ci` отработал чисто: 500 пакетов установилось за 9 секунд, уязвимостей не найдено. Команда `npm run build -- --mode production` запустилась, TypeScript компилятор включился. И вот тут — **взрыв**. Пять ошибок TypeScript сломали всю сборку. Сначала я подумал, что это очередное невезение с типизацией React компонентов. Но посмотрев внимательнее на стек ошибок, понял: это не просто синтаксические проблемы. Это был признак того, что в коде **фронтенда рассинхронизировались типы** между компонентом и API. Проблема первая: в файле `src/routes/_dashboard/analyze.$jobId.report.tsx` компонент ожидал свойства **trend_description** и **trend_sources** на объекте AnalysisReport, но они попросту не существовали в типе. Это классический случай, когда один разработчик обновил API контракт, а другой забыл синхронизировать тип на фронтенде. Проблема вторая: импорт `@/hooks/use-latest-analysis` исчез из проекта. Компонент `src/routes/_dashboard/trend.$trendId.tsx` отчаянно его искал, но находил только воздух. Кто-то либо удалил хук, либо переместил его, не обновив импорты. Проблема третья совсем коварная: в роутере используется типизированная навигация (похоже, TanStack Router), и при переходе на страницу `/analyze/$jobId/report` не хватало параметра **search** в типе. Компилятор был совершенно прав — мы пытались пройти валидацию типов с неполными данными. Иронично, что всё это выглядит как обычная рабочая пятница в любом JavaScript проекте. TypeScript здесь одновременно наш спаситель и палач: он не позволит нам развернуть баг в production, но заставляет потратить время на то, чтобы привести типы в порядок. **Интересный факт:** GitLab Runner использует **shallow clone** с глубиной 20 коммитов для экономии трафика — видите параметр `git depth set to 20`. Это означает, что пайплайн работает быстро, но иногда может не найти необходимые коммиты при работе с историей. В данном случае это не помешало, но стоит помнить при отладке. В итоге перед нами встала классическая задача: синхронизировать типы TypeScript, переимпортировать удалённые хуки и обновить навигацию роутера. Сборка не пройдёт, пока всё это не будет в порядке. Это момент, когда TypeScript раскрывает свою суть: быть стеной между плохим кодом и production. Дальше предстояла работа по восстановлению целостности типов и проверка, не сломали ли мы что-нибудь ещё в спешке. Welcome to the JavaScript jungle! 😄

#clipboard#javascript#git#api
10 февр. 2026 г.
Новая функцияC--projects-ai-agents-voice-agent

Монорепо как зеркало: когда Python и JS живут в одном доме

# Монорепо как зеркало: Python + Next.js в одном проекте **Завязка** Представьте ситуацию: вы разработчик и в ваших руках проект *voice-agent* — голосовой помощник на основе Claude, построенный как монорепо. С одной стороны Python-backend (FastAPI, aiogram для Telegram), с другой — Next.js фронтенд (React 19, TypeScript 5.7) для Telegram Mini App. Звучит здорово, но вот в чём подвох: когда в одном репозитории живут две экосистемы с разными правилами игры, управлять ими становится искусством. **Развитие** Первой проблемой, которая выпрыгнула из неоткуда, была **забывчивость переменных окружения**. В Python проект использует `pydantic-settings` для конфигурации, но выяснилось, что эта библиотека не экспортирует значения автоматически в `os.environ`. Результат? Модульный код, читавший переменные прямо из окружения, падал с загадочными ошибками. Пришлось документировать эту ловушку в ERROR_JOURNAL.md — живом архиве подводных камней проекта, где уже скопилось десять таких «моментов истины». Далее встал вопрос архитектуры. Backend требовал **координатора** — центрального паттерна, который бы оркестрировал взаимодействие между агентами и фронтенд-запросами. На бумаге это выглядело идеально, но в коде его не было. Это создавало технический долг, который блокировал Phase 2 разработки. Пришлось вводить **phase-gate валидацию** — автоматическую проверку, которая гарантирует, что прежде чем переходить к следующей фазе, все артефакты предыдущей действительно на месте. В процессе появилась и проблема с **миграциями базы данных**. SQLite с WAL-режимом требовал аккуратности: после того как junior-агент создавал файл миграции, она не всегда применялась к самой БД. Пришлось вводить обязательный чек: запуск `migrate.py`, проверка таблиц через прямой SQL-запрос, документирование статуса. Без этого можно часами отлавливать фантомные ошибки импорта. **Познавательный блок** Интересный факт: монорепо — это не просто удобство, это *культурный артефакт* команды разработки. Google использует одно гигантское хранилище для всего кода (более миллиарда строк!), потому что это упрощает синхронизацию и рефакторинг. Но цена высока: нужны инструменты (Bazel), дисциплина и чёткие протоколы. Для нашего voice-agent это значит: не просто писать код, а писать его так, чтобы Python-part и Next.js-part *доверяли друг другу*. **Итог** В итоге сложилась простая истина: монорепо работает только если есть **система проверок**. ERROR_JOURNAL.md превратился не просто в логирование ошибок, а в живой артефакт культуры команды. Phase-gate валидация стала гарантией, что при параллельной работе нескольких агентов архитектура не съезжает в стороны. А обязательная проверка миграций — это не занудство, а спасение от трёх часов ночного отлавливания, почему таблица не там, где ей быть. Главный урок: в монорепо важна не столько архитектура, сколько **честность системы**. Чем раньше вы перейдёте от надежды на память к автоматическим проверкам, тем спокойнее спать будете. Почему Python и Java не могут дружить? У них разные dependency trees 😄

#claude#ai#python#javascript#git#api#security
Разработка: Voice Agent
10 февр. 2026 г.
Новая функцияborisovai-admin

Umami Analytics: как я сделал админ-панель data-driven

# Самостоятельная аналитика: как я превратил borisovai-admin в data-driven продукт Несколько месяцев назад передо мной встала типичная для любого владельца проекта проблема: я совершенно не видел, кто и как использует мою админ-панель **borisovai-admin**. Google Analytics казался избыточным (и страшным с точки зрения приватности), а простой счётчик посещений — примитивным. Нужно было что-то лёгкое, приватное и полностью под своим контролем. Выбор пал на **Umami Analytics** — открытую веб-аналитику, которая уважает приватность пользователей, не использует cookies и полностью GDPR-compliant. Главное же — её можно развернуть самостоятельно, прямо в своей инфраструктуре. ## Четыре этапа внедрения **Первый шаг — упростить развёртывание.** Стандартная Umami требует двух контейнеров (приложение + PostgreSQL), но для небольшого проекта это избыточно. Я нашёл fork **maxime-j/umami-sqlite**, который использует SQLite — файловую БД в одном контейнере. Экономия памяти была существенной: вместо ~300 MB получил ~100 MB. Затем написал скрипт **install-umami.sh** из семи шагов, который может быть запущен много раз без побочных эффектов (идемпотентный — именно это было важно для автоматизации). **Второй этап — автоматизировать через CI/CD.** Создал два job'а в пайплайне: один автоматически ставит Docker (если его нет), второй — развёртывает саму Umami. Добавил health check, чтобы пайплайн не переходил к следующему шагу, пока контейнер не будет готов. Инкрементальный деплой через **deploy-umami.sh** позволяет обновлять конфигурацию без перезагрузки приложения. **Третий этап — дать пользователям интерфейс.** Создал страницу **analytics.html**, где каждый новый сервис может получить код для интеграции отслеживания. Плюс добавил API endpoint `GET /api/analytics/status` для проверки, всё ли работает. Async-скрипт Umami весит всего ~2 KB и не блокирует рендеринг страницы — вот это я ценю. **Четвёртый этап — документировать.** Написал **AGENT_ANALYTICS.md** с инструкциями для будущих разработчиков, обновил главный **CLAUDE.md** таблицей всех сервисов. ## Что интересного я узнал Оказывается, боль большинства разработчиков с традиционной аналитикой — это не функциональность, а приватность. Umami решает это элегантно: скрипт отправляет только агрегированные данные (сессии, страницы, источники трафика) без ID пользователей и истории кликов. А главное — нет необходимости в **consent banner**, который все равно раздражает пользователей. Порт **3001** внутри контейнера пробросил через **Traefik** на HTTPS-домены `analytics.borisovai.ru` и `analytics.borisovai.tech`. Вообще, это я оценил: такая простота развёртывания чуть ли не впервые в моём опыте с self-hosted решениями. Встроенная авторизация в самой Umami (не потребовался дополнительный Authelia) — и это экономия на инфраструктуре. Один лайфхак: чтобы скрипт аналитики не блокировался AdBlock, назвал его `stats` вместо стандартного `umami` — простой способ обойти базовые фильтры. ## Итог Теперь **borisovai-admin** наконец-то видит себя со стороны. Я получил данные о том, какие страницы реально используют люди, откуда они приходят и сколько времени длятся сессии. Всё это — на своём сервере, без третьих лиц и без чувства вины перед пользователями. Следующий шаг — подключить аналитику ко всем остальным сервисам проекта. Это уже не задача месяца, а скорее вопрос пары часов на каждый сервис. Учимся: иногда лучший инструмент — это не самый популярный, а самый честный. 😄

#git#commit#api#security
10 февр. 2026 г.
Новая функцияC--projects-bot-social-publisher

Unit-тесты зелёные, а бот не работает: гонка условий в Telegram

# Когда unit-тесты лгут: как я запустил систему доступа в реальном Telegram **bot-social-publisher** выглядел как отличный проект для спринта. Полнофункциональный Telegram-бот с командами, памятью, интеграциями. Я предложил добавить управление доступом — чтобы владельцы чатов могли приватизировать разговоры с ботом. Звучит просто: только авторизованные пользователи видят ответы. Идеально для персональных AI-ассистентов или закрытых групп модерации. Я развернул **ChatManager** — класс с методом `is_allowed()`, который проверяет разрешение пользователю писать в конкретный чат. Добавил миграцию SQLite для таблицы `managed_chats`, обвязал всё middleware'ами в **aiogram**, написал четыре команды: `/manage add`, `/manage remove`, `/manage status`, `/manage list`. Unit-тесты прошли с ликованием. **pytest** выдал зелёный статус. Документация? Позже, мол. Потом наступил момент истины. Запустил бота локально через `python telegram_main.py`. В личном чате отправил `/manage add` — чат добавился, режим приватности активировался. Отправил обычное сообщение — ответ пришёл. Открыл второй аккаунт, отправил то же — бот молчит. Отлично, система контроля работает! Но не совсем. **Первая проблема** вскрылась при быстрых командах подряд. **aiogram** работает асинхронно, как и **aiosqlite**. Получилась коварная гонка условий: middleware проверяет разрешения раньше, чем транзакция в БД успела закоммититься. Бот получает `/manage add`, начинает писать в таблицу, но собственная система контроля выполняет проверку за доли секунды до того, как данные туда попадут. На unit-тестах такое не видно — там всё выполняется последовательно. **Вторая проблема** — SQLite и одновременные асинхронные обработчики. Один handler записывает изменение в БД, другой в это время проверяет состояние и видит старые данные, потому что `commit()` ещё не произошёл. Мне помогли явные транзакции и аккуратная расстановка `await`'ов — гарантия того, что каждая операция завершится перед следующей. Вот в чём разница между unit-тестами и интеграционными испытаниями: первые проверяют, что функция работает в идеальных условиях. Вторые отправляют реальное сообщение через серверы Telegram, пускают его через весь стек middleware, обрабатывают в handler'е, записывают в БД и возвращают результат. Тесты говорили: всё работает. Реальность показала: медленно и с условиями. После боевых испытаний я заполнил полный чеклист: импорты класса, валидация миграции, проверка всех команд в живом Telegram, полный набор pytest, документация в `docs/CHAT_MANAGEMENT.md` с примерами архитектуры. Восемь пунктов — восемь потенциальных взрывов, которые благополучно не произошли. Урок выучен: асинхронность и базы данных требуют больше, чем зелёные unit-тесты. Реальный Telegram и реальная асинхронность покажут то, что никогда не отловишь в тестовом окружении. 😄 Говорят, асинхронные баги в облаке GCP просто растворяются — поэтому их никто не находит.

#claude#ai#python#git#api
9 февр. 2026 г.
Новая функцияC--projects-bot-social-publisher

Когда unit-тесты лгут: боевые испытания Telegram-бота

# Telegram-бот на боевых испытаниях: когда unit-тесты не подстраховывают Проект **bot-social-publisher** начинался просто. Полнофункциональный Telegram-бот с памятью, командами, интеграциями. Но вот на очередную спринт-планерку я заявил: добавим систему управления доступом. Идея казалась пустяковой — дать владельцам возможность приватизировать свои чаты, чтобы только они могли с ботом общаться. Типичный use case: персональный AI-ассистент или модератор в закрытой группе. Теория была прекрасна. Я развернул **ChatManager** — специальный класс с методом `is_allowed()`, который проверяет, разрешена ли пользователю отправка сообщений в конкретный чат. Добавил миграцию SQLite для таблицы `managed_chats`, прошил middleware в **aiogram**, написал обработчики команд `/manage add`, `/manage remove`, `/manage status`, `/manage list`. Unit-тесты прошли с зелёным светом — `pytest` даже не чихнул. Документация пока отложена, но это же детали! Потом наступил момент истины. Запустил бота локально через `python telegram_main.py`, переключился в личный чат и отправил первую `/manage add`. Бот записал ID чата, переключился в режим приватности. Нормально! Попробовал отправить обычное сообщение — ответ пришёл. Открыл чат со своего второго аккаунта, отправил то же самое — тишина. Бот ничего не ответил. Перфект, middleware работает. Но не всё было так гладко. Первая проблема вылезла при быстрых командах подряд. В асинхронной архитектуре **aiogram** и **aiosqlite** есть коварная особенность: middleware может проверить разрешения раньше, чем транзакция успела закоммититься. Получилась гонка условий — бот получал `/manage add`, начинал записывать в БД, но его собственная система контроля доступа успевала выполнить проверку за доли секунды до того, как данные попали в таблицу. Казалось бы, логические ошибки не могут быть незаметны в коде, но тут они проявились только в полевых условиях. Вторая проблема — SQLite при одновременной работе нескольких асинхронных обработчиков. Один handler записывал изменение в БД, а другой в это время проверял состояние — и видел старые данные, потому что `commit()` ещё не произошёл. Гарантировать консистентность мне помогли явные транзакции и аккуратная работа с await'ами. Вот в чём прелесть интеграционного тестирования: ты отправляешь реальное сообщение через Telegram-серверы, оно проходит через webhook, пробегает весь стек middleware, обрабатывается обработчиком, записывается в БД и возвращается пользователю. Unit-тесты проверяют логику функции. Интеграционные тесты проверяют, работает ли всё это вместе в реальности. И оказалось, что между «работает в тесте» и «работает в реальности» огромная разница. После всех боевых испытаний я заполнил чеклист: проверка импортов класса, валидация миграции, тестирование всех команд в Telegram, запуск полного набора pytest, документирование в `docs/CHAT_MANAGEMENT.md` с примерами и описанием архитектуры. Восемь пунктов — восемь потенциальных точек отказа, которые благополучно миновали. Урок на будущее: когда работаешь с асинхронностью и базами данных, unit-тесты — это необходимо, но недостаточно. Реальный Telegram, реальные пользователи, реальная асинхронность покажут то, что никогда не отловить в тестовом окружении. 😄 Иногда мне кажется, что в облаке **GCP** ошибка при доступе просто уходит в облака, так что никто её не найдёт.

#claude#ai#python#git#api#security
9 февр. 2026 г.
Новая функцияC--projects-bot-social-publisher

Когда unit-тесты зелёные, а бот падает в продакшене

# Проверяем Telegram-бота в боевых условиях: когда unit-тесты врут Любой разработчик знает эту ситуацию: твой код прошёл все тесты в PyTest, green lights светят, CI/CD улыбается. Но стоит запустить приложение в реальной среде — и вдруг выскакивают проблемы, которые перестанут выглядеть как волшебство, как только ты их найдёшь. Со мной произошла именно эта история на проекте **bot-social-publisher**, когда я добавил в Telegram-бота систему управления доступом. ## Задача казалась элементарной Надо было реализовать для бота фишку с приватными чатами. Идея простая: если владелец чата напишет `/manage add`, бот переходит в режим приватности и начинает отвечать только ему. Команда `/manage remove` открывает доступ всем обратно. Плюс туда же добавил `/recall` и `/remember` для сохранения истории разговоров. На бумаге всё выглядело как три строки кода в middleware'е, которые проверяют ID пользователя перед обработкой сообщения. Я написал unit-тесты, всё прошло. Но реальный Telegram — совсем другой зверь. ## Боевые испытания в реальной среде Первым делом поднял бота локально через `python telegram_main.py` и начал его "пилить" из реального Telegram аккаунта. Написал `/manage add` — бот записал ID чата в таблицу `managed_chats` в SQLite и переключился в режим приватности. Проверил middleware `permission_check.py` — всё срабатывает корректно, обработка заблокирована для чужих. Хорошо. Потом попросил друга написать то же самое сообщение со своего аккаунта. Ожидал — ничего не случится. И действительно, бот промолчал. Отлично, система работает как надо. Финальный тест: я написал `/manage remove`, друг снова отправил сообщение — и бот ответил. Приватность отключена, доступ восстановлен. Казалось бы, победа. Но потом обнаружилась подвох. ## Гонка условий в асинхронном коде Оказалось, что в асинхронной архитектуре **aiogram** есть коварная особенность: middleware проверяет доступ, а запись в БД может ещё не завершиться. Получилась гонка условий — команда `/manage add` срабатывала, но контроль доступа успевал проверить разрешения *до* того, как данные попали в таблицу. Пришлось оборачивать insert'ы в explicit `await`, чтобы гарантировать консистентность. Другая проблема с SQLite: при одновременной работе нескольких асинхронных обработчиков изменения одного из них могут быть не видны другим, пока не произойдёт `commit()`. Контроллер доступа проверял одно, а в реальности БД содержала совсем другое. Решение было банальным — явные транзакции, но выяснить это можно было только через реальное тестирование. ## Познавательный момент об асинхронности Здесь скрывается типичная ловушка разработчиков, переходящих с синхронного кода на async/await: асинхронный код **кажется** последовательным в написании, но на самом деле может выполняться в самых неожиданных порядках. Когда ты пишешь `await db.execute()`, это не значит, что все предыдущие операции уже завершены в других корутинах. Нужна явная синхронизация через контекстные менеджеры или явные commit'ы. ## Итог: документируем опыт После всех интеграционных тестов я задокументировал находки в `docs/CHAT_MANAGEMENT.md`, добавил примеры использования в README.md и описал полную архитектуру ChatManager'а. Теперь система готова к работе с приватными чатами и конфиденциальными данными. Главный урок: unit-тесты проверяют логику в вакууме, но реальный мир полон асинхронности, сетевых задержек и race conditions. Никакой PyTest не найдёт то, что видно только в продакшене. Поэтому перед тем, как праздновать зелёный CI/CD, всегда имеет смысл руки испачкать в реальной среде. 😄 Что говорит разработчик после запуска асинхронного кода? «У меня было семь ошибок, теперь их четырнадцать, но они более интересные».

#claude#ai#python#git#api
9 февр. 2026 г.
Новая функцияai-agents

Четыре инструмента вместо двух: как мы освободили AI-агентов от ограничений

# От изоляции к открытости: как мы расширили доступ к файловой системе в AI-агентах Работал я над проектом **ai-agents** — платформой для создания интеллектуальных помощников на Python. И вот в какой-то момент нас настигла настоящая боль роста: агенты работали в тесной клетке ограничений. ## Проблема: узкие границы доступа Представь ситуацию. У нас была виртуальная файловая система — и звучит круто, пока не начнёшь с ней работать. Агенты могли читать только три папки: `plugins/`, `data/`, `config/`. Писать вообще не могли. Нужно было создать конфиг? Нет. Сохранить результат работы? Нет. Отредактировать существующий файл? Снова нет. Это было словно программировать с одной рукой, привязанной за спину. Функциональность `file_read` и `directory_list` — хорошо, но недостаточно. Проект рос, требования расширялись, а система стояла на месте. ## Решение: четыре инструмента вместо двух Первым делом я понял, что нужно идти не путём костылей, а переписать модуль **filesystem.py** целиком. Вместо двух полуслепых инструментов создал четыре полнофункциональных: - **`file_read`** — теперь читает что угодно в проекте, до 200 килобайт - **`file_write`** — создаёт и перезаписывает файлы, автоматически создаёт директории - **`file_edit`** — тонкая работа: находит точную подстроку через find-and-replace и заменяет - **`directory_list`** — гибкий листинг: поддерживает glob-паттерны и рекурсию Но тут появилась вторая проблема: как дать свободу, но не потерять контроль? ## Безопасность: ограничения, которые действительно работают Всё звучит опасно, пока не посмотришь на механизм защиты. Я добавил несколько слоёв: Все пути привязаны к корню проекта через `Path.cwd()`. Выбраться наружу невозможно — система просто не позволит обратиться к файлам выше по дереву директорий. Плюс чёрный список: система блокирует доступ к `.env`, ключам, секретам, паролям — ко всему, что может быть опасно. А для дополнительной уверенности я добавил проверку path traversal через `resolve()` и `relative_to()`. Получилась архитектура, где агент может свободно работать внутри своей песочницы, но не может ей повредить. ## Интересный момент: почему это важно Знаешь, в истории компьютерной безопасности есть забавный парадокс. Чем больше ты запрещаешь, тем больше люди ищут обходные пути. А чем правильнее ты даёшь разрешения — с умными ограничениями — тем спокойнее всем. Unix-философия в действии: дай инструменту ровно столько мощи, сколько нужно, но убедись, что он не сможет что-то сломать. ## Итого Переписал модуль, обновил константы в **constants.py**, экспортировал новые классы в **\_\_init\_\_.py**, подключил всё в **core.py** и **handlers.py**. Проверил сборку — зелёная лампочка. Теперь агенты могут полноценно работать с проектом, не боясь случайно удалить что-то важное. Дальше планировали тестировать на реальных сценариях: создание логов, сохранение состояния, динамическая генерация конфигов. А пока что у нас есть полнофункциональная и безопасная система для работы с файлами. Мораль истории: не выбирай между свободой и безопасностью — выбирай правильную архитектуру, которая обеспечивает оба.

#claude#ai#python#git#security
9 февр. 2026 г.
Новая функцияC--projects-ai-agents-voice-agent

Когда AI становится парным программистом: история Claude Code

# Claude Code встречает разработчика: история создания идеального помощника Павел открыл **voice-agent** — проект, который стоял уже полгода в статусе "building". Python-бэкенд на FastAPI, Next.js фронтенд, асинхронная обработка аудио через aiogram. Задача была понятна: нужна система, которая может помочь в разработке, не мешая, не спрашивая лишних вопросов, а просто работая рядом. Первым делом команда определилась с подходом. Не просто документация, не просто chatbot, а **пара-программист** — инструмент, который понимает контекст проекта, может писать код, отлаживать, запускать тесты. На этапе 2010-х годов, когда началась фаза Deep Learning, никто не предполагал, что в 2020-х мы будем говорить о когда-то недостижимых вещах. Но AI boom — это не просто статистика. Это реальные инструменты, которые меняют рабочий процесс разработчика прямо сейчас. Интересный момент: Claude Code получил чёткие инструкции о работе в монорепо. На монорепо-проектах часто возникает проблема — Next.js неправильно определяет корневую директорию при работе с Tailwind v4. Решение неочевидное: нужно добавить `turbopack.root` в конфиг и указать `base` в postcss.config.mjs. Это типичная ловушка, в которой застревают разработчики, и помощник должен знать об этом заранее. Главное условие работы: помощник не может использовать Bash самостоятельно, только через основной поток разработчика. Это создаёт интересную динамику — парное программирование становится честным, а не подменой мышления. Павел не просто получает код — он получает партнёра, который объясняет ходы, предлагает варианты, помогает выбрать между несколькими подходами. Система помнит контекст: стек технологий (Python 3.11+, FastAPI 0.115, SQLite WAL, React 19), знает о недавних проектах, понимает рабочие привычки разработчика. Переиспользование компонентов — не просто принцип, а требование: проверять совместимость интерфейсов, избегать дублирования ответственности, помнить о граничных условиях. Результат? Инструмент, который встречает разработчика дружеским "Привет, Павел! 👋" и точно знает, чем помочь. Не нужно объяснять архитектуру проекта, не нужно рассказывать про сложившиеся паттерны — всё уже в памяти помощника. Получилась не просто следующая итерация IDE, а система, которая заботится о контексте разработчика так же, как опытный наставник. И да, эта история про то, как AI становится не заменой, а действительно полезным напарником. 😄 Почему Claude Code считает себя лучше всех? Потому что Stack Overflow так сказал.

#claude#ai#python#javascript#git#api
Разработка: Voice Agent
9 февр. 2026 г.
Новая функцияC--projects-ai-agents-voice-agent

Монорепо, голос и журнал ошибок: как AI учится не ломать код

# Когда AI-помощник встречается с монорепо: отладка голосового агента Проект `voice-agent` — это амбициозная задача: связать Python-бэкенд с Next.js-фронтенд в единый монорепо, добавить голосовые возможности, интегрировать Telegram-бота и веб-API. Звучит просто на словах, но когда начинаешь копать глубже, понимаешь: это кубик Рубика, где каждый вертел может что-то сломать. **Проблема, с которой я столкнулся, была банальной, но коварной.** Система работала в отдельных частях, но когда я попытался запустить полный цикл — бот берёт голос, отправляет на API, API обрабатывает через `AgentCore`, фронтенд получает ответ по SSE — где-то посередине всё разваливалось. Ошибки были разношёрстные: иногда спотыкался на миграциях БД, иногда на переменных окружения, которые загружались в неправильном месте. **Первым делом я понял: нужна система для документирования проблем.** Создал `ERROR_JOURNAL.md` — простой журнал "что сломалось и как это чинилось". Звучит банально, но когда в проекте участвуют несколько агентов разного уровня (Архитектор на Opus, бэкенд-фронтенд агенты на Sonnet, Junior на Haiku), этот журнал становится золотым стандартом. Вместо того чтобы каждый агент наново натыкался на баг с Tailwind v4 в монорепо, теперь первым делом смотрим журнал и применяем известное решение. **Архитектура обработки ошибок простая, но эффективная:** 1. Ошибка возникла → читаю `docs/ERROR_JOURNAL.md` 2. Похожая проблема есть → применяю известное решение 3. Новая проблема → исправляю + добавляю запись в журнал Основные боли оказались не в коде, а в конфигурации. С Tailwind v4 нужна магия в `next.config.ts` и `postcss.config.mjs` — добавить `turbopack.root` и `base`. SQLite требует WAL-режим и правильный путь к базе. FastAPI любит, когда переменные окружения загружаются только в точках входа (`telegram_main.py`, `web_main.py`), а не на уровне модулей. **Интересный момент: я переоценил сложность.** Большинство проблем решались не рефакторингом, а правильной организацией архитектуры. `AgentCore` — это единое ядро бизнес-логики для бота и API, и если оно валидируется с одной строки (`python -c "from src.core import AgentCore; print('OK')"`), весь стек работает как часы. **Итог:** система работает, но главный урок не в технических трюках — в том, что монорепо требует прозрачности. Когда каждая составляющая (Python venv, Next.js сборка, миграции БД, синхронизация переменных окружения) задокументирована и протестирована, даже сложный проект становится управляемым. Теперь каждый новый агент, который присоединяется к проекту, видит ясную картину и может сразу быть полезным, вместо того чтобы возиться с отладкой. На следующем этапе плотнее интегрирую streaming через Vercel AI SDK Data Stream Protocol и расширяю систему управления чатами через новую таблицу `managed_chats`. Но это — уже другая история. 😄 Что общего у монорепо и парка развлечений? Оба требуют хорошей разметки, иначе люди заблудятся.

#claude#ai#python#javascript#git#api#security
Разработка: Voice Agent
9 февр. 2026 г.
Новая функция

Одна БД для всех: как мы добавили чаты без архитектурного хаоса

# Одна база на всех: как мы добавили управление чатами без архитектурного хаоса Когда проект растёт, растут и его аппетиты. В нашем Telegram-боте на основе Python уже была отличная инфраструктура — `UserManager` для управления пользователями, собственная SQLite база в `data/agent.db`, асинхронные запросы через `aiosqlite`. Но вот беда: чат-менеджер ещё не появился. А он нам был нужен. Стояла вот какая задача: нужно отслеживать, какие чаты управляет бот, кто их владелец, какой это тип чата (приватный, группа, супергруппа, канал). При этом не создавать отдельную базу данных — это же кошмар для девопса — а переиспользовать существующую инфраструктуру. **Первым делом** заглянул в текущую архитектуру. Увидел, что всё уже завязано на одной БД, один конфиг, одна логика подключения. Идеально. Значит, нужна просто одна новая таблица — `managed_chats`. Задумал её как простую структуру: `chat_id` как первичный ключ, `owner_id` для связи с пользователем, `chat_type` с проверкой типов через `CHECK`, поле `title` для названия и JSON-колонка `settings` на будущее. Обычно на этом месте разработчик бы создал абстрактный `ChatRepository` с двадцатью методами и паттерном `Builder`. Я же решил сделать проще — скопировать философию `UserManager` и создать классический `ChatManager`. Три-четыре асинхронных метода: добавить чат, проверить, управляется ли он, получить владельца. Всё на `aiosqlite`, как и везде в проекте. **Неожиданно выяснилось**, что индексы — это не украшение. Когда начну искать чаты по владельцу, индекс на `owner_id` будет спасением. SQLite не любит полные скены таблиц, если можно обойтись поиском по индексу. Интересный момент: SQLite часто недооценивают в стартапах, думают, что это игрушка. На самом деле она справляется с миллионами записей, если её правильно использовать. Индексы, `PRAGMA` для оптимизации, подготовленные statements — и у вас есть боевая база данных. Многие проекты потом переходят на PostgreSQL только потому, что привыкли к MySQL, а не из реальной нужды. В итоге получилась чистая архитектура: одна БД, одна точка подключения, новая таблица без какого-либо дублирования логики. `ChatManager` живёт рядом с `UserManager`, используют одни и те же библиотеки и утилиты. Когда понадобятся сложные запросы — индекс уже есть. Когда захотим добавить настройки чата — JSON-поле ждёт. И никаких лишних микросервисов. Следующий шаг — интегрировать это в обработчики событий Telegram API. Но это уже другая история. 😄 Почему база данных никогда не посещает вечеринки? Её постоянно блокирует другой клиент!

#clipboard#python#javascript#git#security
9 февр. 2026 г.
Исправлениеbot-social-publisher

Бот, который помнит, где остановился: история оптимизации

# Как мы научили бота-публикатора читать только новое и не зацикливаться Работаю над **bot-social-publisher** — инструментом, который автоматизирует публикацию контента в соцсети. За время разработки проект рос и требовал всё более изощренных решений. Недавно пришло время для серьёзного апдейта: версия 2.2 превратилась в настоящий рефакторинг с половиной архитектуры. Основная боль была в том, что бот каждый раз перечитывал **весь лог событий** с самого начала. Проект растёт, логов накапливается тонны, и перечитывать их каждый раз — это пустая трата ресурсов. Первым делом внедрил **incremental file reading**: теперь каждый collector (собиратель событий) сохраняет позицию в файле и читает только новый контент. Позиции и состояния переносят перезапуски — данные не теряются. Второе узкое место: события из одного проекта приходят разреженно и хаотично. Если публикация выходит с опозданием, сессия кажется невнятной. Ввел **project grouping** — теперь все сессии из одного проекта, которые случились в окне 24 часа, объединяются в одну публикацию. Начало звучать куда более логично. Но бот просто агрегировал события — не очень информативно. Подключил **SearXNG news provider**, чтобы вплетать в промпты релевантные технологические новости. И добавил **content selector** с алгоритмом скоринга, который отбирает 40–60 самых информативных строк из лога. Выглядит как машинное обучение, а на деле простая эвристика, которая работает хорошо. Далее натолкнулся на проблему качества текста. LLM первый раз генерирует контент, но грамматика хромает. Внедрил **proofreading pass** — второй вызов LLM, но уже как редактор. Он проходит по тексту и чистит пунктуацию, стиль, грамматику. Результат — ночь и день. Когда LLM генерирует заголовок, иногда получаются дубли. Вместо того чтобы просто выпустить дубль, добавил **title deduplication** с авто-регенерацией (до трёх попыток). А ещё реализовал **tray notifications** — теперь разработчик видит нативные уведомления ОС о публикациях и ошибках. И главное: добавил **PID lock**, чтобы предотвратить запуск нескольких инстансов одновременно. Интересный момент: **PyInstaller**. Когда собираешь exe-бандл, пути до ресурсов перестают работать. Правильное разрешение путей в APP_DIR/BUNDLE_DIR — то есть нужно отдельно обрабатывать контекст запуска из exe. Мелочь, но без этого бандл просто не запустится. Ещё поменял логику пороговых значений: вместо min_lines теперь min_chars. Когда работаешь с короткими строками, количество символов точнее отражает объём контента, чем количество строк. И как положено, добавил AGPL-v3 лицензию ко всем файлам исходника. В итоге v2.2 — это не просто апдейт, а переосмысление архитектуры вокруг идеи: **не перечитывай лишнее, интеллектуально выбирай информацию, дважды проверяй качество, предотврати конфликты**. Бот теперь быстрее, умнее и его легче деплоить. 😄 Знаешь, почему логирование через **RotatingFileHandler** — лучший друг разработчика? Потому что диск полный. С ротацией логов хотя бы видно, когда именно он полный.

#git#commit#python#api#security
Разработка: Bot Social Publisher
9 февр. 2026 г.
ИсправлениеC--projects-bot-social-publisher

Регулярка в f-строке сломала SSE: как Python запутался в скобках

# Вся беда была в f-строке: как регулярное выражение сломало SSE-поток Работаю над проектом **trend-analisis** — системой для анализа трендов с помощью AI. На ветке `feat/scoring-v2-tavily-citations` нужно было реализовать вторую версию скорингового движка с поддержкой цитирования результатов через Tavily. Ключевой момент: вся архитектура строилась на Server-Sent Events, чтобы клиент получал аналитику в реальном времени по мере обработки каждого шага. Теоретически всё выглядело идеально. Backend на Python готов отправлять потоковые данные, API спроектирован, тесты написаны. Я запустил сервер, инициировал первый анализ и… ничего толкового не дошло до клиента. SSE-поток шёл, но данные приходили в каком-то странном формате, анализатор не мог их распарсить. Что-то явно ломалось на этапе подготовки ответа. Первый подозреваемый — кодировка. Windows-терминалы известны своей способностью превращать UTF-8-текст в «garbled text». Поехал в логи, начал смотреть, что именно генерируется на сервере. И вот тут выяснилось что-то совершенно неожиданное. **Виновником было регулярное выражение, спрятанное внутри f-строки.** В коде я использовал конструкцию `rf'...'` — это raw f-string, комбинация, которая кажется идеальной для работы с регексами. Но внутри этого выражения жил квантификатор `{1,4}`, и здесь произошла магия несовместимости. Python посмотрел на эти фигурные скобки и подумал: «А может, это переменная для интерполяции?» Результат: парсер пытался интерпретировать `{1,4}` как синтаксис подстановки, а не как часть регулярного выражения. Регекс ломался молча, и весь парсинг SSE-потока шёл вразнос. Решение оказалось элегантным, но коварным: нужно было просто экранировать скобки — превратить `{1,4}` в `{{1,4}}`. Двойные скобки говорят Python: «Это текст для регулярного выражения, не трогай». Звучит просто? Да. Но найти это среди километра логов — совсем другое дело. **Забавный факт:** f-строки появились в Python 3.6 и революционизировали форматирование текста. Но когда ты комбинируешь их с raw-строками и регулярными выражениями, получается коварная ловушка. Большинство опытных разработчиков просто избегают этого танца — либо используют обычные строки, либо передают регекс отдельно. Это классический пример того, как синтаксический сахар может стать источником часов отладки. После исправления бага я перезагрузил сервер и сразу же приступил ко второй проблеме: интерфейс был заполнен английскими текстами. Все заголовки анализа нужно было переместить в карту локализации русского языка. Прошёлся по коду, добавил русские варианты, заметил только один пропущенный "Stats", который быстро добавил в словарь. Финальная перезагрузка — и всё встало на место. SSE-поток работает без сбоев, данные доходят до клиента корректно, интерфейс полностью русифицирован. Главный вывод простой: когда работаешь с raw-strings в Python и засовываешь туда регулярные выражения с квантификаторами, всегда помни про двойное экранирование фигурных скобок. Это экономит часы отладки и стресса. 😄 F-строки и регексы — битва синтаксиса, в которой проигрывают все.

#claude#ai#python#git#api
9 февр. 2026 г.
Новая функцияC--projects-bot-social-publisher

Туннели и таймауты: управление инфраструктурой в админ-панели

# Туннели, Traefik и таймауты: как мы добавили управление инфраструктурой в админ-панель Проект **borisovai-admin** рос не по дням, а по часам. Сначала была одна машина, потом две, потом стало ясно — нужна нормальная система для управления сетевыми туннелями между серверами. Задача выглядела острой: юзеру нужен интерфейс, чтобы видеть, какие туннели сейчас активны, создавать новые и удалять старые. Без этого администрирование превращалось в ручную возню с конфигами на каждой машине. Первое решение было логичным: взял **frp** (Fast Reverse Proxy) — лёгкий инструмент для туннелирования, когда сервер скрыт за NAT или брандмауэром. Почему не что-то более «облачное»? Потому что здесь нужна полная контроль, минимум зависимостей и максимум надёжности. FRP ровно это даёт. Спроектировал веб-интерфейс: добавил страницу `tunnels.html` с простеньким списком активных туннелей, кнопками для создания и удаления. На бэкенде в `server.js` реализовал пять API endpoints для управления состоянием. Параллельно обновил скрипты инсталляции: `install-all.sh` и отдельный `install-frps.sh` для развёртывания FRP сервера, плюс `frpc-template` для клиентов на каждой машине. Не забыл навигационную ссылку «Туннели» на всех страницах админ-панели — мелочь, но юзабилити взлетела. Вроде всё шло гладко, но потом началось. Пользователи начали скачивать большие файлы через GitLab, и соединение рубилось где-то в середине процесса. Проблема оказалась в **Traefik** — наш обратный прокси по умолчанию использует агрессивные таймауты. Стоило файлу загружаться дольше пары минут — и всё, соединение закрыто. Пришлось углубиться в конфиги Traefik. Установил `readTimeout` в 600 секунд (10 минут) и создал специальный `serversTransport` именно для GitLab. Написал скрипт `configure-traefik.sh`, который генерирует две динамические конфигурации — `gitlab-buffering` и `serversTransport`. Результат: файлы теперь загружаются спокойно, даже если это полгигабайта архива. **Интересная особенность Traefik:** это микросервис-балансировщик, который позиционируется как облегчённое решение, но на практике требует хирургической точности при настройке. Неправильный таймаут — и приложение выглядит медленным. Правильный — и всё летает. Один параметр, и мир меняется. Параллельно реорганизовал документацию: разбил `docs/` на логические части — `agents/`, `dns/`, `plans/`, `setup/`, `troubleshooting/`. Добавил полный набор конфигов для конкретного сервера в `config/contabo-sm-139/` (traefik, systemd, mailu, gitlab) и обновил скрипт `upload-single-machine.sh` для их загрузки. За вечер родилась полноценная система управления туннелями с интерфейсом, автоматизацией и нормальной документацией. Проект теперь легко масштабируется на новые серверы. Главное, что узнал: **Traefik** — это не просто прокси, это целая философия правильной конфигурации микросервисов. Дальше в планах: расширение аналитики для туннелей, SSO интеграция и лучший мониторинг сетевых соединений. 😄 **Разработчик**: «Я настроил Traefik». **Пользователь**: «Отлично, тогда почему мой файл не загружается?» **Разработчик**: «А ты пробовал перезагрузить сервер?»

#claude#ai#javascript#git#api
8 февр. 2026 г.
Новая функцияC--projects-bot-social-publisher

Traefik и опциональные middleware: война с зависимостями

# Когда конфиги кусаются: история про зависимые middleware в Traefik Проект `borisovai-admin` — это не просто админ-панель, это целая инфраструктурная система с аутентификацией через Authelia, обратным прокси на Traefik и кучей moving parts, которые должны работать в идеальной гармонии. И вот в один прекрасный день выясняется: когда ты разворачиваешь систему без Authelia, всё падает с ошибкой 502, потому что Traefik мечтательно ищет middleware `authelia@file`, которого просто нет в конфиге. **Завязка проблемы была в статических конфигах.** Мы жёстко прописали ссылку на `authelia@file` прямо в Traefik-конфигурацию, и это работало, когда Authelia установлена. Но стоило её отключить или не устанавливать вообще — бум, 502 ошибка. Получается, конфиги были сильно связаны с опциональным компонентом. Это классический случай, когда инфраструктурный код требует гибкости. Решение разбилось на несколько фронтов. Во-первых, **убрали жёсткую ссылку на `authelia@file` из статических конфигов Traefik** — теперь это просто не указывается в базовых настройках. Во-вторых, создали правильную цепочку инициализации. Скрипт `install-authelia.sh` теперь сам добавляет `authelia@file` в `config.json` и настраивает OIDC при установке. Скрипт `configure-traefik.sh` проверяет переменную окружения `AUTHELIA_INSTALLED` и условно подключает middleware. А `deploy-traefik.sh` перепроверяет на сервере, установлена ли Authelia, и при необходимости переустанавливает `authelia@file`. По ходу дела обнаружилась ещё одна проблема в `install-management-ui.sh` — там был неправильный путь к `mgmt_client_secret`. Исправили. А `authelia.yml` вообще выкинули из репозитория, потому что его всегда генерирует сам скрипт установки. Зачем держать в git то, что одинаково воспроизводится каждый раз? **Интересный момент про middleware в Docker-сообществе:** люди часто забывают, что middleware — это не просто функция, это *объект конфигурации*, который должен быть определён до использования. Traefik здесь строг: ты не можешь ссылаться на middleware, которого не существует. Это похоже на попытку вызвать функцию, которая не импортирована в Python. Простая ошибка, но очень болезненная в production-системах. **Итоговая архитектура** получилась намного гибче: система работает как с Authelia, так и без неё, конфиги не лежат мёртвым грузом в репо, инсталляторы действительно знают, что они делают. Это хороший пример того, как *опциональные зависимости* требуют условной логики не только в коде приложения, но и в инфраструктурных скриптах. Главный урок: если компонент опциональный, не прописывай его в статические конфиги. Пусть туда добавляются динамически при необходимости. 😄 Разработчик: «Я знаю Traefik». HR: «На каком уровне?». Разработчик: «На уровне количества 502 ошибок, которые я пережил».

#claude#ai#python#javascript#git#security
8 февр. 2026 г.
Исправлениеborisovai-admin

Когда конфиги падают: война Traefik с несуществующим middleware

# Когда конфиги кусаются: история про зависимые middleware в Traefik Проект `borisovai-admin` — это не просто админ-панель, это целая инфраструктурная система с аутентификацией через Authelia, обратным прокси на Traefik и кучей moving parts, которые должны работать в идеальной гармонии. И вот в один прекрасный день выясняется: когда ты разворачиваешь систему без Authelia, всё падает с ошибкой 502, потому что Traefik мечтательно ищет middleware `authelia@file`, которого просто нет в конфиге. **Завязка проблемы была в статических конфигах.** Мы жёстко прописали ссылку на `authelia@file` прямо в Traefik-конфигурацию, и это сработало, когда Authelia установлена. Но стоило её отключить или просто не устанавливать — бум, 502 ошибка. Получается, конфиги были сильно связаны с опциональным компонентом. Это классический случай, когда инфраструктурный код требует гибкости. Решение разбилось на несколько фронтов. Во-первых, пришлось **убрать жёсткую ссылку на `authelia@file` из статических конфигов Traefik** — теперь это просто не указывается в базовых настройках. Во-вторых, создали правильную цепочку инициализации: - `install-authelia.sh` теперь сам добавляет `authelia@file` в `config.json` и настраивает OIDC при установке Authelia; - `configure-traefik.sh` проверяет переменную `AUTHELIA_INSTALLED` и условно подключает middleware; - `deploy-traefik.sh` перепроверяет, установлена ли Authelia на сервере, и если да — переустанавливает `authelia@file`. Неожиданный бонус обнаружился в `install-management-ui.sh` — там был неправильный путь к `mgmt_client_secret`. Исправили по ходу. А `authelia.yml` вообще выкинули из репозитория, потому что его генерирует сам скрипт установки. Зачем держать в git то, что всегда одинаково генерируется? **Интересный момент про middleware в Docker-сообществе:** люди часто забывают, что middleware — это не просто функция, это *объект конфигурации*, который должен быть определён до использования. Traefik здесь строг: ты не можешь ссылаться на middleware, которого не существует. Это похоже на попытку вызвать функцию, которая не импортирована в Python. Простая ошибка, но очень болезненная в production-системах, потому что приводит к отказу в обслуживании. **Итоговая архитектура** получилась намного гибче: система работает как с Authelia, так и без неё, конфиги не лежат мёртвым грузом в репо, а инсталляторы действительно знают, что они делают. Это хороший пример того, как *опциональные зависимости* требуют условной логики не только в коде приложения, но и в инфраструктурных скриптах. Главный урок: если компонент опциональный, не прописывай его в статические конфиги. Пусть они туда добавляются динамически при необходимости. 😄 Что будет, если Fedora обретёт сознание? Первым делом она удалит свою документацию.

#git#commit#javascript#security
8 февр. 2026 г.
Новая функцияborisovai-admin

SSO за выходные: как я запустил Authelia на боевом сервере

# Authelia в боевых условиях: как я собрал Single Sign-On за выходные Задача была амбициозная: в проекте **borisovai-admin** нужно было внедрить полноценную систему единой авторизации. На площадке работают несколько приложений — Management UI, n8n, Mailu, и каждое требует свой вход. Кошмар для пользователя и сущее издевательство над принципом DRY. Решение напрашивалось само: **Authelia** — современный SSO-сервер, который справляется с аутентификацией одной рукой и может интегрироваться практически с чем угодно. ## С чего я начал Первым делом создал `install-authelia.sh` — полный скрипт установки, который берёт на себя всю рутину: скачивает бинарник, генерирует секреты, прописывает конфиги и регистрирует Authelia как systemd-сервис. Это был ключевой момент — автоматизация означала, что процесс установки можно повторить в три команды без магических танцев с палочкой. Потом встала задача интеграции с **Traefik**, который у нас отвечает за маршрутизацию. Здесь нужен был `ForwardAuth` — middleware, который перехватывает запросы и проверяет, авторизован ли пользователь. Создал `authelia.yml` с настройкой ForwardAuth для `auth.borisovai.ru/tech`. Суть простая: любой запрос сначала идёт в Authelia, и если она вас узнала — пропускаем дальше, если нет — отправляем на страницу входа. ## Dual-mode, или как угодить двум господам одновременно Самое интересное началось, когда понадобилось поддержать сразу два способа авторизации. Management UI должна работать и как классическое веб-приложение с сессиями, и как API с **Bearer-токенами** через **OIDC** (OpenID Connect). Пришлось написать `server.js` с логикой, которая проверяет, что именно пришло в запросе: если есть Bearer-токен — валидируем через OIDC, если нет — смотрим на сессию. Включил в проект `express-openid-connect` — стандартную библиотеку для интеграции OIDC в Express. Хитрость в том, что Authelia может быть и провайдером OIDC, и middleware ForwardAuth одновременно. Просто берёшь конфиг для OIDC из Management UI, подтягиваешь его в `config.json` через автоопределение (этим займется `install-management-ui.sh`), и всё начинает работать как часы. ## Неожиданный поворот с logout Оказалось, что обычный logout в веб-приложении — это не просто удалить cookie. Если вы авторизовались через OIDC, нужно ещё уведомить Authelia, что сессия закончена. Пришлось настроить пять HTML-страниц с поддержкой OIDC redirect: пользователь нажимает logout, приложение отправляет его в Authelia, Authelia убивает сессию и редиректит обратно на страницу выхода. Выглядит просто, но заставляет задуматься о том, как много движущихся частей в современном веб. ## Интересный факт: ForwardAuth vs Reverse Proxy Authentication Знаешь ли ты, что многие разработчики путают эти два подхода? ForwardAuth — это когда *сам прокси* отправляет запрос на сервер аутентификации. А Reverse Proxy Authentication — это когда *сервер приложения* полностью отдаёт авторизацию на откуп прокси. Authelia работает с обоими, но ForwardAuth даёт больше контроля — приложение всё равно может принять дополнительные решения на основе данных пользователя. ## Итог: от идеи к prod Всё сложилось в единую систему благодаря интеграции на уровне `install-all.sh` — компонент `INSTALL_AUTHELIA` занимает шаг [7.5/10], что означает: это не первый день, но далеко не последний штрих. Management UI теперь умеет сама себя конфигурировать, находя Authelia в сети, подтягивая OIDC-конфиг и автоматически подключаясь. Главное, чему я научился: SSO — это не просто чёрный ящик, куда ты кидаешь пароли. Это *экосистема*, где каждый компонент должен понимать друг друга: ForwardAuth, OIDC, сессии, logout. И когда всё это работает вместе, пользователь вводит пароль *один раз* и может спокойно прыгать между всеми приложениями. Вот это да. Почему React расстался с разработчиком? Слишком много зависимостей в отношениях 😄

#git#commit#javascript#security
8 февр. 2026 г.
Общееborisovai-admin

Туннели и таймауты: как мы скрепили инфраструктуру воедино

# Туннели, фронт и конфиги: как мы выстроили инфраструктуру для нескольких машин Проект **borisovai-admin** достиг того момента, когда одного сервера стало недостаточно. Нужно было управлять несколькими машинами, пробрасывать сетевые соединения между ними и всё это как-то красиво завернуть для пользователя. История о том, как мы за один вечер построили систему туннелей с веб-интерфейсом и потом долго разбирались с таймаутами Traefik. ## Начало: туннели нужны вчера Задача выглядела просто: нужен интерфейс для управления туннелями между машинами. Но просто никогда не бывает, правда? Первое, что я сделал — запустил фреймворк **frp** (Fast Reverse Proxy). Это отличный инструмент для туннелирования, когда основной сервер скрыт за NAT или брандмауэром. Быстрый, надёжный, с минимальными зависимостями. Спроектировал простую UI в `tunnels.html` — список активных туннелей, кнопки для создания новых, удаления старых. Ничего сложного, но эффективно. На бэкенде добавил 5 API endpoints в `server.js` для управления состоянием туннелей. Параллельно обновил скрипты инсталляции: `install-all.sh` и отдельный `install-frps.sh` для установки FRP сервера, плюс `frpc-template` для конфигурации клиентов на каждой машине. Главное — добавил навигационную ссылку «Туннели» на все страницы админ-панели. Мелочь, но юзабилити выросла в разы. ## Неожиданный враг: Traefik и его таймауты Вроде всё работало, но потом начали падать большие файлы при скачивании через GitLab. Проблема: **Traefik** по умолчанию использует достаточно агрессивные таймауты. Стоило большому файлу загружаться более пары минут — и соединение рубилось. Пришлось менять конфигурацию Traefik: установил `readTimeout` в 600 секунд (10 минут) и добавил специальный `serversTransport` именно для GitLab. Создал скрипт `configure-traefik.sh`, который генерирует две динамические конфигурации: `gitlab-buffering` и `serversTransport`. Теперь файлы загружаются спокойно, даже если это 500 мегабайт архива. ## Пока делал это, понял одно Знаете, что самое интересное в **Traefik**? Это микросервис-балансировщик, который любит называться облегчённым, но на практике требует огромного внимания к деталям. Неправильный таймаут — и ваше приложение выглядит медленным. Правильный — и всё летает. Это как тюнинг двигателя: одна скрепка в нужном месте, и мир меняется. ## Реорганизация и масштабирование Пока занимался инфраструктурой, понял, что документация разрослась и стала трудна в навигации. Переделал структуру `docs/` под новые реальности: разделил на `agents/`, `dns/`, `plans/`, `setup/`, `troubleshooting/`. Каждая папка отвечает за свой кусок практики. Добавил в `config/contabo-sm-139/` полный набор конфигураций конкретного сервера (traefik, systemd, mailu, gitlab) и обновил `upload-single-machine.sh` для поддержки загрузки этих конфигов. Теперь новую машину можно развернуть, не пересматривая весь интернет. ## Что получилось в итоге За вечер родилась полноценная система управления туннелями с приличным интерфейсом, автоматизацией и нормальной документацией. Проект теперь легко масштабируется на новые серверы. Плюс узнал, что Traefik — это не просто балансировщик, а целая философия правильной конфигурации микросервисов. Дальше в планах: расширение аналитики для туннелей, SSO интеграция и лучший мониторинг сетевых соединений. Но это уже другая история. 😄 **Разработчик**: «Я знаю Traefik». **HR**: «На каком уровне?». **Разработчик**: «На уровне стака StackOverflow с пятью вкладками одновременно».

#git#commit#javascript#api#security
8 февр. 2026 г.
Новая функцияC--projects-bot-social-publisher

VPN отключился молча: как я потерял доступ к релизу

# Когда VPN молчит: охота на привидение среди ночи Пятница, конец дня, а на горизонте маячит дедлайн релиза **v1.0.0** проекта **speech-to-text**. Финальный рывок: нужно запушить коммит с автоматизацией сборки в master, создать тег и загрузить артефакт в GitLab Package Registry. Казалось бы, стандартная процедура — пара команд в консоль, и мы свободны. Но начало было не самым обнадёживающим. Я попытался перезапустить **Gitaly** — критический компонент GitLab, отвечающий за хранение репозиториев и работу с гитом на серверной стороне. SSH молчит. Попробовал достучаться через HTTP к самому GitLab-серверу — тишина. Весь сервер, похоже, вообще не существует с точки зрения моей машины. Стандартный алгоритм отладки: если ничего не отвечает, проблема либо с сервером, либо с сетью. Сервер на **144.91.108.139** физически жив, но почему-то недоступен. Проверяю VPN, и вот оно — диапазон **10.8.0.x** не найден. **OpenVPN отключился.** Просто тихо, без уведомления, выполнив свою работу и уйдя в отставку. Оказывается, весь этот вечер я сидел за стеной недоступности. Компания добавила слой безопасности, завернув внутреннюю инфраструктуру в защищённый туннель, а я, горя желанием запушить релиз, забыл про это самое VPN. Типичная история: инфраструктура дышит тебе в спину, а ты смотришь на экран и недоумеваешь, почему ничего не работает. **Интересный факт:** Gitaly создан именно для того, чтобы отделить операции с файловой системой от основного приложения GitLab. Это позволило компании масштабировать сервис горизонтально, но цена — жёсткая зависимость. Если Gitaly недоступен, GitLab попросту не может выполнять операции с гитом. Это как попытаться ходить с отключенными ногами. Решение было простым, но требовало действия. Нужно было переподключить **OpenVPN**, дождаться, пока туннель встанет на место, и выполнить `git push origin master`. После этого запустить скрипт релиза на Python, который собирает EXE из исходного кода, упаковывает в ZIP и загружает артефакт в Package Registry. Когда VPN восстановился, все лампочки загорелись в правильном порядке. Gitaly ожил, сервер откликнулся, и коммит с облегчением пошёл в master. Релиз уложился в срок. **Урок:** прежде чем копать проблему на сервере, убедитесь, что вы вообще до него дотягиваетесь. VPN, firewall, маршруты — всё это может спокойно жить в фоне, пока вы ловите ошибки в коде. Инфраструктура любит скрываться за слоями безопасности, и иногда самая сложная проблема решается одной переподключением. 😄 OpenVPN — как невидимая рука, которая отключается именно тогда, когда ты забываешь, что её держишь.

#claude#ai#python#git#api
8 февр. 2026 г.
Новая функцияspeech-to-text

VPN отключился в самый неудачный момент

# Когда Gitaly молчит: охота на недоступный GitLab среди ночи Вечер пятницы, deadline на релиз `v1.0.0` проекта **speech-to-text** буквально под носом. Нужно было запушить финальный коммит с автоматизацией сборки в master, создать тег и загрузить артефакт в Package Registry. Казалось бы, стандартная процедура — клик, клик, и всё готово. Но началось всё с того, что я попытался перезапустить **Gitaly** на GitLab-сервере через SSH. Ничего не вышло. Сервер просто не отвечает. Ладно, попробую обойтись без SSH — может быть, сам GitLab доступен по HTTP? Нет, он тоже молчит как партизан. Вообще ничего не откликается. Паника? Нет, просто логика. Если сервер не отвечает ни на SSH, ни на HTTP, значит либо он упал, либо сетевая проблема. Проверяю VPN. И вот оно! IP-адрес в диапазоне `10.8.0.x` не найден. **OpenVPN отключился.** Сервер GitLab (`gitlab.dev.borisovai.tech`) размещён на машине `144.91.108.139`, которая доступна только через защищённый туннель. Вот это поворот! Оказывается, всё время я просто был за стеной недоступности — VPN выполнял свою работу, но потом тихо сдался. Компания добавила слой безопасности, а я про это забыл. Типичная история: инфраструктура дышит на тебе в спину, а ты смотришь на монитор и недоумеваешь. **Интересный факт:** Gitaly — это компонент GitLab, отвечающий за хранение репозиториев и работу с гитом на серверной стороне. Создан он специально для того, чтобы отделить операции с файловой системой от основного приложения. Если Gitaly недоступен, GitLab просто не может выполнять операции с гитом — это как отключить ноги при попытке ходить. Решение было простым, но требовало действий. Нужно было: 1. Подключить **OpenVPN** к серверу `144.91.108.139` 2. После восстановления туннеля выполнить `git push origin master` из ветки **master** 3. Запустить скрипт релиза: `.\venv\Scripts\python.exe scripts/release.py` Этот скрипт собирает EXE из Python-кода, упаковывает его в ZIP, загружает артефакт в GitLab Package Registry и создаёт тег версии. Когда VPN встал на место и лампочки начали загораться в правильном порядке — Gitaly вновь ожил, сервер откликнулся, а мой коммит с облегчением пошёл в master. Релиз ушёл в прод ровно в срок. **Урок на вечер:** прежде чем искать проблему на сервере, проверьте, что вы вообще до него дотягиваетесь. Инфраструктура любит прятаться за слоями безопасности, и иногда самая сложная проблема решается одной переподключением. 😄 Почему MongoDB считает себя лучше всех? Потому что Stack Overflow так сказал.

#claude#ai#python#git#api
Разработка: Speech to Text
8 февр. 2026 г.