BorisovAI

Блог

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

Изменение кодаborisovai-admin

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

# Запускаем Authelia: логины, пароли и первый вход в админку Проект **borisovai-admin** требовал серьёзной работы с аутентификацией. Стояла простая на первый взгляд задача: развернуть **Authelia** — современный сервер аутентификации и авторизации — и убедиться, что всё работает как надо. Но перед тем как запустить систему в боевых условиях, нужно было разобраться с креденшалами и убедиться, что они безопасно хранятся. Первым делом я заглянул в скрипт установки `install-authelia.sh`. Это был не просто набор команд, а целая инструкция по настройке системы с нуля — 400+ строк, описывающих каждый шаг. И там я нашёл ответ на главный вопрос: логин для Authelia — это просто **`admin`**, а пароль... вот тут начиналось интересное. Оказалось, что пароль хранится в двух местах одновременно. В конфиге Authelia (`/etc/authelia/users_database.yml`) он лежит в виде **Argon2-хеша** — это криптографический алгоритм хеширования, специально разработанный для защиты паролей от перебора. Но на сервере управления (`/etc/management-ui/auth.json`) пароль хранится в открытом виде. Логика понятна: Management UI должна иметь возможность проверить, что введён правильный пароль, но хранить его в открытом виде — это классическая дилемма безопасности. Неожиданно выяснилось, что это не баг, а фича. Разработчики системы сделали так специально: пароль Management UI и пароль администратора Authelia — это один и тот же секрет, синхронизированный между компонентами. Это упрощает управление, но требует осторожности — нужно убедиться, что никто не получит доступ к этим файлам на сервере. Я закоммитил все необходимые изменения в ветку `main` (коммит `e287a26`), и pipeline автоматически задеплоил обновлённые скрипты на продакшн. Теперь, если кому-то понадобится сбросить пароль администратора, достаточно просто зайти на сервер, открыть `/etc/management-ui/auth.json` и посмотреть текущее значение. Не самый secure способ, но он работает, пока файл лежит в защищённой директории с правильными permissions. Главный вывод: при работе с аутентификацией нет мелочей. Каждое хранилище пароля — это потенциальная точка входа для атакующего. **Argon2** защищает от перебора, но открытые пароли в конфигах требуют ещё более строгого контроля доступа. В идеальном мире мы бы использовали системы управления секретами вроде HashiCorp Vault, но для локального dev-сервера такой подход сойдёт. Дальше нужно будет настроить интеграцию Authelia с остальными компонентами системы и убедиться, что она не станет узким местом при масштабировании. Но это история для следующего поста. 😄 Что общего у Scala и подростка? Оба непредсказуемы и требуют постоянного внимания.

#claude#ai#python#javascript#security
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 г.
Новая функцияtrend-analisis

Race condition в системе версионирования: как два ревьюера поймали потерю данных

# Когда два ревьюера находят одни и те же баги: история о том, как система версионирования может потерять данные Работаешь над feature branch `feat/scoring-v2-tavily-citations` в проекте **trend-analisis**, пилишь систему многоуровневого анализа трендов. Задача звучит просто: позволить анализировать один тренд несколько раз с разными параметрами (`depth`, `time_horizon`), сохранять все варианты и отправлять их на фронт. Казалось бы, что может быть проще? Потом коммит отправляешь на ревью двум коллегам. И они оба, независимо друг от друга, находят одну и ту же **критическую ошибку** — race condition в функции `next_version()`. Момент волшебства: когда разные люди пришли к одному выводу, значит, ошибка точно смертельна. Вот что происходит. Функция `next_version()` считает максимальный номер версии анализа для тренда и возвращает `max + 1`. Звучит логично, но представь: два запроса одновременно анализируют один тренд. Оба вызывают `next_version()`, получают одинаковый номер (например, `version=3`), затем пытаются сохранить результат через `save_analysis()`. Один INSERT успешен, второй молча пропадает в чёрной дыре `except Exception: pass`. Данные потеряны, пользователь не узнает о проблеме. Но это ещё не всё. Коллеги заметили вторую проблему: функция видит только завершённые анализы (статус `completed`), поэтому запущенный анализ (статус `running`) остаётся невидимым для системы версионирования. Получается, что второй запрос стартует с того же номера версии, какой уже занят висящим процессом. Классическая ловушка асинхронности. Обнаружилось ещё несколько багов: фронт ожидает получить один объект `getAnalysisForTrend`, а бэкенд начал отправлять массив анализов. TypeScript тип `AnalysisReport` не знает про новые поля (`version`, `depth`, `time_horizon`, `parent_job_id`) — они приходят с сервера и сразу теряются. Параметр `parent_job_id` вообще ни на что не валидируется, что открывает дверь для инъекций. И `depth` может быть любым числом — никакого лимита, хоть 100 передай. **Интересный момент:** многие разработчики думают, что `except Exception: pass` это "временно", но на практике эта конструкция часто уходит в production как постоянное решение, маскируя критические ошибки. Это называется *exception swallowing*, и это один из самых подлых антипаттернов асинхронного кода. Решение оказалось не очень сложным, но требовало думать о транзакциях иначе. Нужно либо **переместить `next_version()` внутрь `save_analysis()`** с retry-логикой на `IntegrityError`, либо использовать **атомарный SQL-запрос `INSERT...SELECT MAX(version)+1`**, чтобы гарантировать уникальность версии за одно действие. Плюс резервировать версию сразу при старте анализа (INSERT со статусом `running`), чтобы параллельные запросы их видели. Для фронта пришлось добавить новый endpoint `getAnalysesForTrend` (а старый `getAnalysisForTrend` оставить для обратной совместимости). TypeScript типы расширены, валидация на `parent_job_id` добавлена, `depth` ограничен до 7 через `Pydantic Field(ge=1, le=7)`. Главный урок: **код, который "работает на примере", и код, который справляется с race conditions, это два разных животных**. Всегда думай про параллелизм, даже если сейчас система однопоточная. И когда два ревьюера независимо находят один и тот же баг — это не совпадение, это сигнал, что нужно переделывать архитектуру, а не чинить синтаксис. 😄 Prometheus: решение проблемы, о существовании которой ты не знал, способом, который не понимаешь.

#claude#ai#python#javascript#api
Разработка: Trend Analisis
8 февр. 2026 г.
Новая функцияC--projects-bot-social-publisher

Мелочь в навигации — архитектура на бэке

# Туннелировать админ-панель: когда мелочь оказывается архитектурой Проект **borisovai-admin** — это управленческая панель для социального паблишера. И вот однажды возникла потребность: нужна видимость в туннели FRP (Fast Reverse Proxy). Казалось — простая фича. Добавить ссылку в навигацию, создать эндпоинты на бэке, вывести данные на фронте. Четыре-пять дней работы, максимум. Началось всё с мелочи: требовалось добавить пункт "Туннели" в навигацию. Но навигация была одна, а HTML-файлов четыре — `index.html`, `tokens.html`, `projects.html`, `dns.html`. И здесь скрывалась первая ловушка: одна опечатка, одна невнимательность при копировании — и пользователь запутается, кликнув на несуществующую ссылку. Пришлось синхронизировать все четыре файла, убедиться, что ссылки находятся на одинаковых позициях в строках 195–238. Мелочь, которую легко упустить при спешке. Но мелочь эта потащила за собой целую архитектуру. На бэке понадобилось добавить две вспомогательные функции в `server.js`: `readFrpsConfig` — для чтения конфигурации FRP-сервера, и `frpsDashboardRequest` — для безопасного запроса к dashboard FRP. Это не просто HTTP-вызовы: это минимальная абстракция, которая облегчит тестирование и повторное использование. Затем пришлось вывести четыре GET-эндпоинта: статус сервера, список активных туннелей с метаинформацией, текущую конфигурацию в JSON и даже генератор `frpc.toml` для скачивания клиентского конфига в один клик. И вот неожиданно выяснилось — сам FRP-сервер ещё нужно установить и запустить. Обновил `install-all.sh`, добавил FRP как опциональный компонент: не все хотят туннели, но кто выбрал — получит полный стек. На фронте создал новую страницу `tunnels.html` с тремя блоками: карточка статуса (живой ли FRP), список туннелей с автообновлением каждые 10 секунд (классический полинг, проще WebSocket'а для этого масштаба) и генератор конфига для клиента. **Интересный факт**: полинг через `setInterval` кажется древним подходом, но именно он спасает от overengineering'а. WebSocket требует поддержки на обеих сторонах, fallback'и на старых браузерах, управление жизненным циклом соединения. Для обновления статуса раз в 10 секунд это overkill. Главное — не забыть очистить интервал при размонтировании компонента, иначе получишь утечку памяти и браузер начнёт отваливаться. Главный урок: даже в мелких фичах скрывается целая архитектура. Одна ссылка в навигации потребовала синхронизации четырёх файлов, пять эндпоинтов на бэке, новую страницу на фронте, обновление скрипта установки. Это не scope creep — это *discovery*. Лучше потратить час на планирование полной цепочки, чем потом переделывать интеграцию, когда уже половина team работает на основе твоей "быстрой фички". 😄 FRP — это когда твой сервер вдруг получает способность ходить в гости через NAT, как путник с волшебным клаком из мультика.

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

GitLab Pages выдал секреты: как мы чуть не залили артефакты в интернет

# Как мы защитили артефакты приватного проекта, но случайно выставили их в интернет Проект `speech-to-text` — это голосовой ввод для веб-приложения. Задача казалась стандартной: настроить автоматизированный релиз артефактов через CI/CD. Но когда я начал копать, обнаружилась забавная особенность GitLab Pages, которая чуть не стала залогом безопасности нашего проекта. ## Когда публичное скрывается за приватным Первым делом я исследовал варианты хранения и раздачи собранных ZIP-файлов. В репозитории всё приватное — исходники защищены, доступ ограничен. Проверил **GitLab Releases** — отличное решение, вот только возникла проблема: как скачивать артефакты с публичного фронтенда, если сам проект закрыт? Тогда я посмотрел на **GitLab Package Registry** — тоже приватный по умолчанию. Но потом наткнулся на странное: GitLab Pages генерирует статические сайты, которые всегда публичны *независимо от приватности самого проекта*. То есть даже если репо закрыт для всех, Pages будут доступны по ссылке для любого, кто её узнает. **Неожиданно выяснилось:** это не баг, а фича. Pages часто используют именно для этого — расшарить артефакты или документацию публично, не давая при этом доступ в репо. ## Как я построил конвейер Архитектура получилась такой: скрипт на Python собирает проект, упаковывает в ZIP и загружает в **GitLab Package Registry**. Затем я создал CI pipeline, который срабатывает на тег вида `v*` (семантическое версионирование). Pipeline скачивает ZIP из Package Registry, деплоит его на GitLab Pages (делает доступным по публичной ссылке), обновляет версию в Strapi и создаёт Release в GitLab. Для работы потребилась переменная `CI_GITLAB_TOKEN` с соответствующим доступом — её я забил в CI Variables с флагами *protected* и *masked*, чтобы она не оказалась в логах сборки. Версионирование жёсткое: версия указывается в `src/__init__.py`, оттуда её подхватывает скрипт при сборке. Архивы называются в стиле `VoiceInput-v1.0.0.zip`. ## Идея, которая в голову не пришла Интересный момент: изначально я беспокоился, что приватный проект станет помехой. На деле оказалось, что Pages — это идеальное решение для раздачи артефактов, которые не нужно прятать, но и не нужно давать доступ к исходникам. Классический сценарий для скачиваемых утилит, библиотек или сборок. Теперь релиз — это одна команда: `.\venv\Scripts\python.exe scripts/release.py`. Скрипт собирает, архивирует, загружает, пушит тег. А CI сам позаботится о Pages, Strapi и Release. 😄 Почему GitLab Pages пошёл в терапию? Потому что у него была сложная личная жизнь — он был публичным, но никто об этом не знал!

#claude#ai#python#git#api#security
Разработка: Speech to Text
8 февр. 2026 г.
Новая функцияtrend-analisis

Тесты падают: как найти виновника, когда это наследство

# Когда тесты ломаются, но ты не виноват: история отладки в проекте trend-analysis Представь ситуацию: ты вносишь изменения в систему анализа трендов, коммитишь в ветку `feat/scoring-v2-tavily-citations`, запускаешь тесты — и вот, шесть тестов падают красными крестами. Сердце замирает. Первый вопрос: это моя вина или наследство от предков? ## Охота на виновника начинается В проекте `trend-analysis` я добавлял новые параметры к функции `_run_analysis`: `time_horizon` и `parent_job_id` как именованные аргументы с дефолтными значениями. На бумаге — всё обратно совместимо. На тестах — красный экран смерти. Первым подозреваемым стала функция `next_version()`. Её вызов зависит от импорта `DB_PATH`, и я подумал: может быть, мокирование в тестах не сработало? Но нет — логика показала, что `next_version()` вообще не должна вызваться, потому что в тесте `trend_id=None`, а вызов обёрнут в условие `if trend_id:`. Второй подозреваемый: `graph_builder_agent`. Эта штука вызывается со специальным параметром `progress_callback=on_zone_progress`, но её мок в тестах — просто лямбда-функция, которая принимает только один позиционный аргумент: ```python lambda s: {...} # вот так мокируют ``` А я вызываю её с дополнительным именованным аргументом. Лямбда восстаёт против `**kwargs`! ## Разворот сюжета Но подождите. Я начал копать логику коммитов и осознал: эта проблема существует ещё до моих изменений. Параметр `progress_callback` был добавлен *раньше*, в одном из предыдущих PR, но тест так и не был обновлён под эту функциональность. Я найденный баг не создавал, я его просто разбудил. Все шесть падающих тестов — это **pre-existing issues**, наследство от ранних итераций разработки. Мои изменения сами по себе не ломают функционал, они полностью обратно совместимы. ## Что дальше? Решили остановиться на стадии прототипа для валидации концепции. На бэкенде я уже поднял: - миграции БД для версионирования анализов (добавил `version`, `depth`, `time_horizon`, `parent_job_id`); - новые функции `next_version()` и `find_analyses_by_trend()` в `analysis_store.py`; - обновлённые Pydantic-модели в `schemas.py`; - API endpoints с автоинкрементом версий в `routes.py`. Фронтенд получил интерактивный HTML-прототип с четырьмя экранами: временная шкала анализов тренда, навигация между версиями с дельта-полосой, unified/side-by-side сравнение версий и группировка отчётов по трендам. ## Урок на память Когда читаешь падающий тест, первое правило: не сразу ищи свою вину. Иногда это старый долг проекта, который ждал своего часа. Отделение pre-existing issues от собственных ошибок — это половина успеха в отладке. И всегда проверяй логику вызовов функций: лямбда-функция, которая не ожидает `**kwargs`, будет молча восставать против незнакомых параметров. Так что: мои изменения безопасны, архитектура готова к следующей фазе, а шесть тестов ждут своего героя, который их наконец-то заимпортирует. 😄

#claude#ai#python#api#security
Разработка: Trend Analisis
8 февр. 2026 г.
Новая функцияC--projects-bot-social-publisher

От плоской базы к дереву версий: как переделать архитектуру анализов

# Когда один анализ становится деревом версий: история архитектурной трансформации Проект **bot-social-publisher** уже имел HTML-прототип интеллектуальной системы анализа трендов, но вот беда — архитектура данных была плоской, как блин. Одна версия анализа на каждый тренд. Задача звучала просто: сделать систему, которая помнит об эволюции анализов, позволяет углублять исследования и ветвить их в разные направления. Именно это отличает боевую систему от прототипа. Начал я со скучного, но критически важного — внимательно прочитал существующий `analysis_store.py`. Там уже жила база на SQLite, асинхронный доступ через **aiosqlite**, несколько таблиц для анализов и источников. Но это была просто полка, а не полнофункциональный архив версий. Первое, что я понял: нужна вертикаль связей между анализами. **Фаза первая: переделка схемы.** Добавил четыре колонки в таблицу `analyses`: `version` (номер итерации), `depth` (глубина исследования), `time_horizon` (временной диапазон — неделя, месяц, год) и `parent_job_id` (ссылка на родительский анализ). Это не просто поля — они становятся скелетом, на котором держится вся система версионирования. Когда пользователь просит «Анализируй глубже» или «Расширь горизонт», система создаёт новую версию, которая помнит о своей предшественнице. **Фаза вторая: переписывание логики.** Функция `save_analysis()` была примитивна. Переделал её так, чтобы она автоматически вычисляла номер версии — если анализируете тренд, который уже видели, то это версия 2, а не перезапись версии 1. Добавил `next_version()` для расчёта следующего номера, `find_analyses_by_trend()` для выборки всех версий тренда и `list_analyses_grouped()` для иерархической организации результатов. **Фаза третья: API слой.** Обновил Pydantic-схемы, добавил поддержку параметра `parent_job_id` в `AnalyzeRequest`, чтобы фронтенд мог явно указать, от какого анализа отталкиваться. Выписал новый параметр `grouped` — если его передать, вернётся вся иерархия версий со всеми связями. Вот тут началось интересное. Запустил тесты — один из них падал: `test_crawler_item_to_schema_with_composite`. Первым делом подумал: «Это я сломал». Но нет, оказалось, это *pre-existing issue*, не имеющий отношения к моим изменениям. Забавный момент: как легко можно записать себе проблему, которая была задолго до тебя. **Интересный факт о SQLite и миграциях.** В Python для SQLite нет ничего вроде Django ORM с его волшебством. Миграции пишешь вручную: буквально SQL-запросы в функциях. `ALTER TABLE` и точка. Это делает миграции прозрачными, понятными, предсказуемыми. SQLite не любит сложные трансформации, поэтому разработчики привыкли быть честными перед памятью и временем выполнения. Архитектура готова. Теперь система может обрабатывать сценарии, о которых шла речь в брифе: анализ разветвляется, углубляется, но всегда помнит свою родословную. Следующий этап — фронтенд, который красиво это выведет и позволит пользователю управлять версиями. Но это совсем другая история. 😄 Моя мораль: если SQLite говорит, что миграция должна быть явной — слушайте, потому что скрытая магия всегда дороже.

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

Как система экспертов перестала взрываться и начала расти

# МОЯ растёт: как система экспертов научилась не взрываться Проект **llm-analisis** — это исследование смеси экспертов (Mixture of Experts, MoE) для обработки сложных данных. Задача на этом этапе была простая, но коварная: система достигла baseline 96.7% точности, но дальше расти отказывалась. И вот когда начинаешь что-то менять — бум, либо эксперты коллапсируют (умирают все кроме одного), либо система начинает неконтролируемо расщепляться. Первым делом выяснилось, что в коде была классическая ошибка: данные для вычисления diversity loss проходили напрямую в экспертов, минуя **model.projection**. Это как подавать сырое масло в двигатель вместо обработанного топлива — система работает, но неправильно. Исправили, и baseline встал на место. Но проблемы начались, когда включили режим роста. Система начала добавлять новых экспертов, и тут два монстра вышли одновременно. Первый — **expert collapse**: из 12 экспертов только 3 получали градиенты, остальные 9 просто сидели без дела, как программисты на планерке. Второй — **growth explosion**: система добавляла по 10 новых экспертов подряд, вместо того чтобы контролировать процесс. Решение пришло в три шага. Добавили **growth cooldown** на 5 эпох — механизм, который останавливает спешку, дав системе время войти в режим. Вместо бешеных 10 сплитов подряд стал ровный, контролируемый рост. Второе — включили **entropy maximization** для load balancing: система теперь активно пытается использовать все эксперты, поощряя их к работе. Результат: мёртвые эксперты воскресли. Было 10 из 12 неактивных, стало все три полностью в деле (84%, 79%, 37% нагрузки соответственно). Третье — пересчитали историю точности, чтобы GO/NO-GO отчёт был честным. И вот интересный факт: идея entropy-based балансирования экспертов в MoE приходит из теории информации 1940-х годов, но в контексте нейросетей её применяют едва ли не недавно. Большинство реализаций просто игнорируют проблему, пока система работает, но как только система начинает расти — всё падает. Lesson learned: лучше потратить час на правильную балансировку, чем две недели на дебаг коллапса. Результат: за 14 эпох система выросла с 96.7% до **97.1%** — не огромно, но честно и стабильно. Все артефакты разложены по местам: отчёт в reports/phase1-moe-growth-results.md, MASTER-SUMMARY обновлён, три файла модели (moe.py, growth.py, train_mnist.py) лежат в seed-net/phase1/. Дальше началось самое интересное — **Phase 2**, где морфогенное поле на основе реакции Schnakenberg будет управлять тем, где и когда растёт система. Но это уже другая история. Оказывается, что система экспертов — это как управление командой: нужна дисциплина (cooldown), справедливое распределение задач (entropy) и честный учёт результатов.

#claude#ai#python
Разработка: LLM Analisis
8 февр. 2026 г.
Изменение кодаtrend-analisis

Как версионировать анализы трендов без потери масштабируемости

# Строим интеллектуальную систему анализа трендов: от прототипа к масштабируемой архитектуре Проект **trend-analysis** стоял на пороге серьёзного апгрейда. На столе была задача: переделать весь бэкенд так, чтобы система могла хранить *несколько версий анализа* одного тренда, отслеживать глубину исследования и временные горизонты. Иначе говоря — нужна была полноценная система версионирования с поддержкой иерархических запросов. ## Когда прототип становится боевой системой HTML-прототип уже был готов, но теперь нужна была *настоящая* архитектура. Первым делом я разобрался с существующим кодом: посмотрел, как устроена текущая схема базы данных, как работают Store-функции, как связаны между собой таблицы. Картина была стандартной: SQLite, асинхронный доступ через aiosqlite, несколько таблиц для анализов и источников. Но текущая структура была плоской — одна версия анализа на тренд. Нужно было всё переделать. ## Три фазы трансформации **Фаза 1: новая архитектура данных.** Добавил колонки для версии, глубины анализа, временного горизонта и ссылки на родительский анализ (parent_job_id). Это позволило связывать версии анализов в цепочку — когда пользователь просит «глубже проанализировать» или «расширить временной диапазон», система создаёт новую версию, которая знает о своём предке. Переписал все конвертеры данных из БД в объекты Python: добавил `_row_to_version_summary` для отдельной версии и `_row_to_grouped_summary` для группировки по тренду. **Фаза 2: API слой.** Обновил Pydantic-схемы, чтобы они знали о версионировании. Переписал `_run_analysis` — теперь она вычисляет номер версии автоматически, берёт из истории максимальный и добавляет единицу. Добавил поддержку параметра `parent_job_id` в `AnalyzeRequest`, чтобы фронтенд мог явно указать, от какого анализа отталкиваться. Выписал новый параметр endpoint `grouped` — если передать его, вернётся группировка по тренду со всеми версиями. Внес изменения в три точки: `analyze_trend` получает `time_horizon`, `get_analysis_for_trend` теперь возвращает ВСЕ версии (а не одну), `get_analyses` поддерживает фильтр по группировке. ## Когда тесты врут Вот здесь начался интересный момент. Запустил тесты бэкенда — один из них упорно падал. `test_crawler_item_to_schema_with_composite` кричал об ошибке. Первым делом подумал: «Это я что-то сломал». Но потом внимательнее посмотрел — оказалось, это *pre-existing issue*, не имеющий отношения к моим изменениям. Забавно, как легко можно развесить себе «ошибок программиста» там, где нужно просто пропустить неработающий тест. ## Интересный факт о миграциях БД Знаете, когда я добавлял новые колонки в существующую таблицу? Оказалось, что в Python-экосистеме для SQLite есть классный паттерн: просто описываешь новую миграцию как функцию, которая выполняет ALTER TABLE. SQLite не любит сложные трансформации, поэтому разработчики привыкли писать миграции вручную — буквально SQL-запросы. Это делает миграции прозрачными и понятными, не как в Django с его ORM-магией. ## Что дальше Архитектура готова. Три фазы реализации — и система способна обрабатывать сложные сценарии: пользователь может запросить анализ, затем попросить углубить его, система создаст новую версию, но будет помнить о предыдущей. Всё можно будет вывести либо плоским списком, либо иерархической структурой через параметр `grouped`. Следующий этап — фронтенд, который будет это всё красиво отображать и управлять версиями. Но это уже совсем другая история. Мораль: если тест падает и это не твоя вина, иногда лучше просто его пропустить и продолжить жить дальше.

#claude#ai#python#api
Разработка: Trend Analisis
8 февр. 2026 г.
Новая функцияllm-analisis

Как остановить Expert 0 от захвата власти в нейросети

# Смирили Mixture of Experts: как остановить экспертов от захвата власти Проект llm-analisis — это исследование динамических систем экспертов для глубокого обучения. Вроде звучит наукообразно, но на практике означает одно: мы собрали архитектуру **Mixture of Experts**, где роутер направляет данные между специализированными нейросетями, и попросили её самой расти. Красивая идея, только выросло совсем не то, что ожидалось. ## Когда эксперты мутировали На первых запусках система вела себя странно: из 12 заранее добавленных экспертов реально работали только двое (Expert 0 с 84% нагрузки и Expert 1 с 88%). Остальные десять были просто мёртвым грузом. Одновременно growth-механизм (алгоритм, отвечающий за размножение экспертов) срабатывал каждую эпоху подряд — 8 экспертов за раз, потом 17, потом ещё больше. Это была не эволюция, а экспоненциальный хаос. Точность застыла на 97.0–97.3% и не росла дальше. Добавляй новых экспертов или нет — ничего не меняется. Типичная ситуация: чем больше участников в процессе, тем больше бюрократии, тем меньше эффективности. ## Три пути к спасению Задача была простая: сломать эту систему правильно. Первым делом добавил **cooldown-механизм** после каждого успешного роста — пять эпох, когда новые эксперты не появляются. Пусть система сначала разберётся, зачем ей вообще эти новички. Во-вторых, внедрил **load balancing loss** в роутер, чтобы он учился распределять нагрузку равномерно, а не зависал на двух избранных. В-третьих, ослабил seed-модель: снизил **TARGET_ACC с 0.98 до 0.97** и уменьшил **HIDDEN_DIM с 12 до 6**, чтобы система не засыпала на лаврах. Неожиданно выяснилось, что максимизация entropy в выходе роутера — это почти волшебство. Просто заставляешь его распределять нагрузку равномернее, и вот уже все три эксперта работают с разумной нагрузкой (84%, 79%, 37% вместо прежних 84%, 88%, 0%). ## Что получилось На третьей попытке всё сработало. Seed-модель из трёх экспертов стабилизировалась на 96.7–97.0% за восемь эпох. На девятой эпохе Firefly-алгоритм деликатно разделил первого эксперта, появился третий. Load balancing ловко заставил его работать — entropy выросла с 0.48 до 1.07. А главное: growth сработал ровно один раз вместо этого бесконечного взрывного роста. Целевая точность в 97.11% достигнута на четырнадцатой эпохе. Вывод банален, но эффективен: иногда самая мощная система требует не расширения, а **дисциплины**. Cooldown, балансировка, осмысленные ограничения — и хаос превращается в эволюцию. --- **Кстати:** Что общего у Svelte и кота? Оба делают только то, что хотят, и игнорируют инструкции 😄

#claude#ai#python
Разработка: LLM Analisis
8 февр. 2026 г.
Новая функцияC--projects-bot-social-publisher

Забытая память: почему бот не помнил ключевых фактов

# Включи память: или как я нашёл потерянный ключ в своём же коде Проблема началась с простого вопроса пользователя: «Помнишь, я вчера рассказывал про своего кота?» Голосовой агент проекта **bot-social-publisher** затормозился и честно признался — не помнит. А ведь целая система персистентной памяти сидела в исходниках, готовая к работе. Задача казалась острой: почему бот забывает своих пользователей? Когда я открыл архитектуру, глаза разбежались. Там была вся красота: **Claude Haiku** извлекал ключевые факты из диалогов, **векторные эмбеддинги** превращали текст в семантический поиск, **SQLite** хранил историю, а система дедупликации следила, чтобы старые сведения не плодились бесконечно. Всё это было написано, протестировано, готово к боевому использованию. Но почему-то попросту не работало. Первым делом я прошёл по цепочке инициализации памяти. Логика была изящной: система слушает диалог, выделяет факты через Haiku, конвертирует их в векторные представления, сохраняет в базу, и при каждом новом сообщении от пользователя вспоминает релевантные события. Должно было работать идеально. Но этого не было. Потом я наткнулся на проклятую строку в конфигурации: **`MEMORY_EMBEDDING_PROVIDER=ollama`** в `.env`. Или, точнее, её отсутствие. Вся система требовала трёхступенчатой настройки: Первое — включить саму память в переменных окружения. Второе — указать, где живёт **Ollama**, локальный сервис для генерации эмбеддингов (обычно `http://localhost:11434`). Третье — убедиться, что модель **nomic-embed-text** загружена и готова превращать текст в вектора. Казалось бы, ничего сложного. Но вот в чём суть: когда система отключена по умолчанию, а документация молчит об этом, разработчик начинает писать заново. Я чуть не попал в эту ловушку — полез переделывать архитектуру, пока не заметил, что ключи уже в кармане. Когда я наконец активировал память, бот ожил. Он узнавал пользователей по именам, помнил их истории, шутки, предпочтения. Диалоги стали живыми и личными. Задача, которая казалась архитектурным провалом, оказалась обычным конфигурационным недосмотром. Это важный урок: когда работаешь со сложными системами, прежде чем писать новый код, **всегда проверь, не отключено ли уже готовое решение**. Лучший код — тот, который уже написан. Нужно только не забыть его включить. 😄 Иногда самая сложная инженерная задача решается одной строкой в конфиге.

#claude#ai#python#javascript#api#security
8 февр. 2026 г.
Новая функцияborisovai-admin

Туннели в админ-панели: простая идея, сложная реализация

# Система туннелей для admin-панели: от идеи к функциональности Когда работаешь над **borisovai-admin** — панелью управления инфраструктурой — рано или поздно встречаешься с проблемой удалённого доступа к сервисам. Задача была классической: нужно добавить в админ-панель возможность управления FRP-туннелями (Fast Reverse Proxy). Это скромные 5 шагов, которые, как выяснилось, требовали куда больше внимания к деталям, чем казалось изначально. **Завязка простая.** Пользователь должен видеть, какие туннели сейчас активны, какой статус у FRP-сервера, и уметь сгенерировать конфиг для клиентской части. Всё это через красивый интерфейс прямо в админ-панели. Типичный запрос, но именно в таких задачах проявляются все неожиданные подводные камни. **Первым делом** обновил навигацию — добавил ссылку "Туннели" во все четыре HTML-файла (index.html, tokens.html, projects.html, dns.html). Казалось бы, мелочь, но когда навигация должна быть идентична на каждой странице, нужно быть аккуратнее: всего одна опечатка — и юзер потеряется. Все ссылки расположены на одинаковых позициях в строках 195–238, что удобно для поддержки. **Потом столкнулся с архитектурой бэкенда.** В server.js добавил две вспомогательные функции: `readFrpsConfig` для чтения конфигурации FRP-сервера и `frpsDashboardRequest` для безопасного запроса данных к dashboard FRP. Это не просто HTTP-вызовы — это минимальная абстракция, которая упрощает тестирование и повторное использование. Далее идут четыре GET-эндпоинта: 1. Статус FRP-сервера (жив ли?) 2. Список активных туннелей с метаинформацией 3. Текущая конфигурация в JSON 4. Генерация `frpc.toml` — клиентского конфига, который можно скачать одной кнопкой **Неожиданно выяснилось** — FRP-сервер нужно ещё установить и запустить. Поэтому обновил скрипт install-all.sh: добавил FRP как опциональный компонент установки. Это важно, потому что не все инсталляции нуждаются в туннелях, а если выбрал — получишь полный стек. **На фронте** создал новую страницу tunnels.html с тремя блоками: - **Карточка статуса** — простая информация о том, работает ли FRP - **Список туннелей** с авто-обновлением каждые 10 секунд (классический полинг, проще чем WebSocket для такого масштаба) - **Генератор клиентского конфига** — вводишь параметры, видишь готовый `frpc.toml` **Интересный факт про FRP:** это вообще проект из Китая (автор — fatedier), но в экосистеме DevOps он стал де-факто стандартом для туннелирования благодаря простоте и надёжности. Многие не знают, что FRP может работать не только как reverse proxy, но и как VPN, и даже как load balancer — просто конфиг нужен другой. **В итоге** получилась полнофункциональная система управления туннелями, интегрированная в админ-панель. Теперь администратор может с одного места видеть всё: какие туннели работают, генерировать конфиги для новых серверов, проверять статус. Документация пошла в CLAUDE.md, чтобы следующий разработчик не переобнаруживал велосипед. Главный урок: даже в мелких фичах типа "добавить ссылку в навигацию" скрывается целая архитектура. Лучше потратить час на планирование, чем потом переделывать интеграцию FRP. 😄 FRP — это когда твой сервер вдруг получает способность ходить в гости через NAT, как путник с волшебным клаком.

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

Голосовой агент с памятью: как мы научили Claude работать асинхронно

# Голосовой агент встретил Claude Code: как мы строили персистентного помощника Когда я открыл проект **voice-agent**, передо мной стояла классическая, но нетривиальная задача: создать полноценного AI-помощника, который бы работал не просто с текстом, но и с голосом, интегрировался в REST API на бэкенде и взаимодействовал с фронтенд-компонентами Next.js. Python на бэкенде, JavaScript на фронте — привычная современная архитектура. Но главный вызов был совсем не в технологиях. **Первым делом я осознал, что это не просто ещё один chatbot.** Нужна была система, которая разбирается в голосовых командах, работает с асинхронными операциями, выполняет команды на файловой системе, интегрируется с документацией и может честно сказать: «Вот тут мне нужна помощь». Начал я с архитектуры — структурировал проект так, чтобы каждый слой отвечал за своё: документация по TMA в `docs/tma/`, структурированный журнал ошибок в `docs/ERROR_JOURNAL.md`, разделение бэкенд-сервисов по функциям. Неожиданно выяснилось, что самая сложная часть — организация информационных потоков. Агент должен знать, где искать справку, как обрабатывать ошибки, когда обратиться к разработчику с уточняющим вопросом. Вот тогда я понял: нужна **встроенная память** — не просто контекст текущей сессии, но настоящее хранилище фактов. Подключил aiosqlite для асинхронного доступа к SQLite, и агент получил возможность запоминать информацию о пользователе, его предпочтениях, даже что-то вроде персональных данных, типа страны проживания. Это открыло целый набор возможностей для персонализации. Агент стал не просто отвечать, а *узнавать* пользователя: «Ты из России? Значит, зафиксирую это и буду учитывать при рекомендациях». **Интересный факт:** мы живём в эпоху ускорения AI-разработок. Deep Learning boom, который начался в 2010-х, в 2020-х годах превратился в настоящий взрыв доступности. Раньше создать сложную AI-систему мог только эксперт с PhD по математике. Теперь разработчик может за выходные собрать полноценного помощника с памятью, асинхронностью и интеграциями — и это стало нормой. **В итоге получилось приложение, которое:** - принимает голосовые команды и преобразует их в действия; - выполняет операции на бэкенде без блокировки интерфейса (спасибо async/await); - запоминает контекст и факты о пользователе; - самостоятельно диагностирует ошибки через структурированный журнал; - честно говорит, когда нужна помощь человека. Дальше впереди оптимизация, расширение функционала, интеграция с реальными API. Проект показал главное: AI-агенты работают лучше всего, когда они знают о своих ограничениях и не пытаются играть в непробиваемого супергероя. Мигрировать с Linux — всё равно что менять колёса на ходу. На самолёте. 😄

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

Версионность анализов: как не запутаться в истории трендов

# Строим сложную архитектуру анализов трендов: как не утонуть в версионности Несколько недель назад встал вопрос, который выглядел просто на первый взгляд: как сделать так, чтобы анализы трендов можно было обновлять, отслеживать изменения и углублять, не теряя историю? Проект **trend-analysis** требовал переоценки всей модели данных. Первый прототип работал, но архитектура не масштабировалась — анализы были привязаны к тренду один-к-одному, как монолит. Нужна была система с версионностью, историей и возможностью углубления. Первым делом я запустил параллельное исследование в три направления: посмотрел на текущую архитектуру хранения данных, проанализировал фронтенд-флоу и продумал новую модель данных. Потом привлёк двух виртуальных экспертов — *аналитика* для продуктового видения и *архитектора* для технической реализации. Они работали одновременно, каждый отдельно собирал требования и пожелания. Результат был интересный. План получился ёмким: **четыре фазы, пятнадцать шагов**. В Phase 1 я добавлял четыре новые колонки в таблицу `analyses`: `version` (auto-increment на тренд), `depth` (глубина анализа), `time_horizon` (горизонт прогноза) и `parent_job_id` (ссылка на предыдущий анализ для построения цепочки углублений). На бэкенде появлялись три критические функции — `next_version()`, `find_analyses_by_trend()` и `list_analyses_grouped()`. Но фронтенд-часть потребовала детализации. Я исследовал текущий UI тренда и понял, что нужна полная переделка. Вместо кнопки «Запустить анализ» должна появиться вертикальная временная шкала со всеми версиями анализа. Каждая версия показывает не только score и confidence, но и тип (INITIAL, RE-ANALYZED, DEEPENED), и дельту относительно предыдущей. На странице отчёта добавлялась навигация между версиями, полоса с метриками и дельтами, кнопки для переанализирования или углубления. Неожиданно выяснилось, что потребуется ещё и сравнение версий. Причём не просто табличное, а с inline-диффом внутри текста отчёта — **word-level** подсветка изменений, параграф за параграфом. Я выбрал библиотеку `diff` (она уже была в node_modules) с `diffLines()` и `diffWords()`, обёрнутой в `useMemo` для производительности. На десяти килобайтах текста расчёт занимает примерно пять миллисекунд — приемлемо. **Важное техническое решение:** версия — это иммутабельный счётчик, который инкрементируется для каждого тренда отдельно. Углубление — это не модификация старого анализа, а создание нового с `depth+2` и ссылкой на parent_job_id. Так мы сохраняем всю историю и можем показать цепочку углублений. Старые записи в БД получают дефолтные значения автоматически — breaking change минимизирован. Перед кодированием я создал HTML-прототип с Tailwind CDN, mock-данными и тремя экранами: страница тренда с timeline анализов, страница отчёта с версионной навигацией и страница со списком отчётов, сгруппированными по тренду. Прототип дал визуальную уверенность, что архитектура работает. Теперь план готов к реализации. Первый шаг — миграция БД и API. Главное в этом проекте не в сложности отдельных компонентов, а в координации между слоями: бэкенд должен вернуть список вместо одного объекта, фронтенд должен правильно отрисовать историю, диффы должны считаться эффективно. Это когда архитектура действительно спасает. *Что сказал Nginx при деплое новой версионности? «Наконец-то вы научились отслеживать историю — я давно это делаю через Etag»* 😄

#claude#ai#python#javascript#api
Разработка: Trend Analisis
8 февр. 2026 г.
Новая функцияscada-coating

Управление 15 подвесками: когда UI решает всё

# Строим интерактивную линию производства: когда один клик решает судьбу 15 подвесок Работаю над SCADA-системой управления конвейерной линией в проекте **scada-coating**. Задача была не для слабонервных: реализовать полноценный интерфейс управления подвесками — теми самыми кареточками, которые возят детали по линии обработки. Проблема была в том, что оператор должен был видеть состояние каждой из 15 подвесок, быстро вызывать нужную, менять её место дислокации, маркировать, переносить в разные хранилища. Всё это нужно было сделать с минимумом кликов и максимумом наглядности. Типичный SCADA-вызов: «Нужно быстро, интуитивно и без ошибок». Решил начать с моделирования данных. Добавил структуры для позиций на линии с типизацией — загрузочные, выгрузочные, хранилища снаряжённых и пустых деталей. Потом описал сами подвески с их состояниями: свободная, в работе, загруженная. Получилось что-то вроде машины состояний, где каждый переход имеет чёткие правила. HTML-часть была увлекательной. Добавил action bar прямо в линию-вью с кнопками: «Вызвать подвеску», «Снарядить», «Переместить», «Все подвески». Плюс модальное окно с выдвижной панелью, где видна информация обо всех 15 подвесках одновременно. Экран на экран — чтобы оператор всегда видел целиком. Но самое интересное началось с JavaScript-логики. Реализовал трёхшаговый визард для вызова подвески: сначала выбираешь, какую подвеску нужна, потом указываешь, куда её вызвать, потом подтверждаешь действие. Каждый шаг валидируется, каждое действие блокируется, если оно невозможно в текущий момент. Контекстные меню при клике на позицию меняются в зависимости от её состояния. Пустая позиция предлагает вызвать подвеску, загруженная — отправить в обработку или переместить в другое хранилище. Логика простая на вид, но за ней стоит матрица правил, которые гарантируют, что ни при каких раскладах оператор не сможет отправить подвеску туда, где её быть не должно. **Занимательный факт:** Цветовая типизация позиций на схеме (синяя для загрузки-выгрузки, зелёная для готовых, жёлтая для пустых) — это не просто красота. В промышленных системах цвет часто решает, получит ли оператор инсульт в 3 часа ночи при аварии. Люди реагируют на цвет в три раза быстрее, чем на текст. Итоговый workflow связал в кучу: нажимаешь кнопку запуска на вкладке «Процесс» — система автоматически переходит на вкладку «Линия» и открывает визард вызова подвески. Оператору не нужно ничего искать. Всё уже перед глазами. Прототип выложил в `scada-operator-v6.html` — можно открыть, нажать кнопки на вкладке «Линия» и посмотреть, как оно живёт. 😄 Что общего у C++ и подростка? Оба непредсказуемы и требуют постоянного внимания

#claude#ai#javascript
Разработка: SCADA Coating
8 февр. 2026 г.