Трекер задач на своём сервере для веб-студии: запуск, перенос истории, русификация
веб-студия (small team, 5 человек) · Веб-разработка / внутренний инструмент проектного менеджмента
Веб-студия из пяти человек вела проекты в чатах: история терялась, единого списка задач не было. Поднял свой трекер и перенёс в него накопленное.
- Docker
- docker-compose
- Caddy
- VPS
- Ubuntu 24.04
- PostgreSQL
- Redis (Valkey)
- RabbitMQ
- MinIO/S3
- Python
- REST API
- Telegram API
- Next.js
- TypeScript
- SQL
- Bash
- Let's Encrypt/TLS
- UFW/fail2ban
В цифрах
- 4 проекта / 198 задач / 357 комментариев / 9 страниц / 12 участников
- импортировано из Telegram в Plane (всего 588 сущностей в state-журнале)
- v1.3.0 (commit cf696d200)
- зафиксированная версия Plane CE
- Up 6 days (healthy)
- 12 контейнеров стека работали стабильно на момент снимка 2026-05-04
- 223 файла / 234 EN-фразы
- пропатчено в apps/web и авто-переведено в рамках доработки RU-локализации
- ru/translations.ts 95KB→143KB
- рост объёма русского словаря после доработки
- retention 7d / 4w / 3m
- политика ротации ежедневных бэкапов (Postgres + MinIO + конфиги)
Ключевое
- Перенёс из переписки 588 записей: 4 проекта, 198 задач, 357 комментариев, 9 страниц и 12 участников — с сохранением того, кто кому отвечал.
- Конвейер переноса можно запускать повторно: при сбое на середине он продолжает с места остановки, а не создаёт дубли.
- Оценки трудозатрат требовали строгой приватности — каждый видит только свои, директор все. Бесплатная версия трекера так не умеет, решил обходным путём.
- Довёл русский перевод интерфейса: свёл глоссарий, сверился с российскими аналогами, перевёл 234 фразы, остававшиеся английскими.
- Настроил ежедневные резервные копии с проверкой целостности и инструкцией восстановления: хранятся неделю, месяц и квартал.
Задача
Веб-студия из пяти человек вела проекты в чатах: по чату на клиента, где вперемешку лежали задания, ссылки на макеты, обсуждения и задачи. Оценки трудозатрат сотрудники присылали директору в личную переписку.
Это перестало масштабироваться. История терялась в прокрутке, единого списка задач не было, а оценки требовали строгой приватности: каждый должен видеть только свои, директор — все. Нужен был собственный трекер задач, не зависящий от облачных сервисов, с перенесённой в него историей и понятным русскоязычной команде интерфейсом.
Что сделано
- Развернул трекер на арендованном сервере с нуля и закрыл сервер от типовых атак: доступ по ключу, автоматические обновления безопасности, настройки базы подобраны под объём памяти машины.
- Настроил защищённое соединение поэтапно: пока адрес сайта ещё расходился по интернету, работал временный сертификат, после — постоянный. Иначе первые дни трекер был бы недоступен.
- Перенёс историю из переписки в трекер. Программа разбирает выгрузки чатов, раскладывает сообщения по двенадцати категориям и превращает ответные цепочки в комментарии к задачам. Конвейер можно запускать повторно: при сбое на середине он продолжает с места остановки, а не создаёт дубли.
- Решил задачу приватности оценок. Бесплатная версия трекера не умеет закрывать отдельные задачи, поэтому проекты с оценками сделаны полностью закрытыми, а правило изоляции финансовых данных работает в обе стороны: они не попадают в публичные проекты, а из закрытых не утекают.
- Настроил ежедневные резервные копии базы, файлов и настроек — с проверкой целостности, ротацией и написанной инструкцией восстановления. Копия, которую не пробовали восстановить, копией не является.
- Довёл русский перевод интерфейса: свёл глоссарий терминов, сверился с российскими аналогами и перевёл фразы, которые штатный перевод оставлял английскими.
Итог
Было
- Проекты и задания — в разрозненных чатах, история терялась в прокрутке
- Оценки трудозатрат — в личной переписке с директором, без разграничения доступа
- Единого списка задач не существует
Стало
- Свой трекер на своём сервере: защищённое соединение, ежедневные копии, инструкция восстановления
- История переписки перенесена в проекты, задачи и комментарии
- Оценки изолированы: их видит только тот, кто их дал, и директор
Студия работает в своём трекере на своём сервере, без зависимости от облачных сервисов и их доступности. Из переписки перенесено 588 записей: 4 проекта, 198 задач, 357 комментариев, 9 страниц и 12 участников — с сохранением того, кто кому отвечал.
Оценки трудозатрат видит только тот, кто их дал, и директор. Русский интерфейс доведён до состояния, когда команде не нужно догадываться: переведены 234 фразы, остававшиеся английскими, и правки затронули 223 файла интерфейса.
Технические детали
Решение и подход
Инфраструктура. Стек Plane запускается официальным релизным docker-compose (APP_RELEASE=v1.3.0, не stable) со встроенными Postgres 15.7, Valkey (Redis), RabbitMQ и MinIO. Поверх — собственный docker-compose.override.yml с healthcheck'ами, security_opt: no-new-privileges и тюнингом Postgres под 4 GB (max_connections=50, shared_buffers=512MB и др.); вариант с mem_limit/cpus сохранён отдельным .disabled-файлом для включения одной командой. Внешний Caddy v2 в отдельном compose-проекте проксирует на внутренний plane-proxy через external docker network. Поскольку домен был зарегистрирован за сутки и A-запись ещё не пропагирована, TLS поднимался поэтапно: старт на tls internal (self-signed) → после пропагации DNS переключение на Let's Encrypt без даунтайма (caddy reload).
Миграция. Дизайн целевой структуры зафиксирован в mapping-design.md до импорта. Сообщения Telegram классифицируются LLM по 12 категориям (requirements / task_actionable / status_update / estimate_request / noise и т.д.); связные ТЗ агрегируются в issue «Brief/ТЗ», reply-цепочки превращаются в дерево комментариев, ссылки на Figma/Drive — в issue links. Состояние пишется в import-state.jsonl (по строке на сущность с plane_id и привязкой к tg_message_id), что делает повторный --apply идемпотентным и даёт rollback. Ключевое нетривиальное решение — приватность оценок: в Plane CE нет per-issue ACL, поэтому оценки выносятся в отдельные private-проекты (Estimates — <Name>, members = [сотрудник, директор]); видимость network=0 выставляется прямым UPDATE в Postgres через SSH→docker exec psql (whitelisted SQL), а «зеркальное правило» вырезает любые суммы/часы из публичных проектов.
Локализация. Разобрана архитектура i18n Plane (кастомный mobx-store + intl-messageformat, локали как TS-модули export default {.} as const, EN-core.ts как fallback). На основе coverage-аудита и бенчмарка российских аналогов составлен глоссарий (Work item → «Задача», Intake → «Заявки», Due date → «Срок выполнения» и т.д.), затем — правки ru/*.ts, патчи hardcoded-строк в apps/web/, передача ru-локали в date-fns и авто-патчер EN→RU-ключей. Так как переводы «запекаются» в Next.js-билд, подготовлен runbook сборки кастомного frontend-образа локально (на VDS сборка падала бы по OOM) и переноса через docker save | ssh | docker load.
Стек и обоснование
- Plane CE v1.3.0 — open-source, AGPL, self-hosted: независимость от облачных PM-сервисов и полный контроль данных команды.
- Docker / docker-compose — официальный способ поставки Plane; воспроизводимость через override-файлы.
- Caddy v2 — автоматический ACME/Let's Encrypt, HTTP/3, лаконичный Caddyfile; вынесен наружу, чтобы не трогать внутренний proxy Plane.
- PostgreSQL / Valkey / RabbitMQ / MinIO — встроенные зависимости Plane; Postgres дополнительно затюнен под 4 GB и используется напрямую (psql) для операции, недоступной через API.
- Python + REST API + Telegram API — пайплайн миграции;
tenacityдля backoff на 429/5xx, idempotent state в JSONL. - LLM-API (единый доступ к нескольким моделям) — компонент пайплайна миграции для классификации сообщений; кэширование удешевляет проход по 500+ сообщениям, смена модели — через env.
- Next.js / TypeScript — слой локализации Plane; правки требуют пересборки frontend-образа.
- UFW / fail2ban / unattended-upgrades / swap — базовая защита и устойчивость одиночного VDS.
Инженерные вызовы
- DNS не пропагирован на старте (домен в зоне ~24 ч) — Let's Encrypt HTTP-01 сразу не прошёл бы; решение — двухфазный TLS (self-signed → LE) с переключением одним reload.
- Жёсткие 4 GB RAM — тюнинг Postgres, лимиты как «потолок», сборка frontend только локально (на VDS — OOM), перенос образа через
docker save/load. - Отсутствие per-issue ACL в Plane CE — приватность финансовых оценок реализована через private-проекты + прямой
UPDATE projects SET network=0в Postgres (whitelisted SQL по SSH), плюс автоматическая вырезка сумм из публичных проектов. - Идемпотентность и откат миграции — JSONL state-machine с привязкой каждой созданной сущности к исходному Telegram-сообщению; повторный прогон пропускает уже залитое.
- Шумные неструктурированные данные Telegram — LLM-классификация по 12 категориям с отбраковкой noise (стикеры, «ок», «+») и ручной доревизией спорных кейсов.
- Безопасность доступа — план изменения
sshd_configс обязательной параллельной safety-сессией и rescue-консолью провайдера на случай локаута.
Услуги в проекте
- деплой
- devops
- миграция
- локализация (i18n)
- бэкап/восстановление
- hardening
- поддержка