Self-hosted Plane CE — деплой, миграция и локализация для веб-студии
веб-студия (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 участников
- v1.3.0 (commit cf696d200)
- Up 6 days (healthy)
- 223 файла / 234 EN-фразы
- ru/translations.ts 95KB→143KB
- retention 7d / 4w / 3m
Контекст и задача
Веб-студия из 5 человек (директор-PM, два разработчика-адаптатора под WordPress/Bitrix, верстальщик-фронтендер, PM) вела проектные данные в разрозненных Telegram-чатах: по чату на клиентский проект (ТЗ, ссылки на Figma, референсы, обсуждения, задачи) плюс отдельные личные чаты с директором, где сотрудники присылали оценки трудозатрат и стоимости. Это не масштабировалось: история терялась, не было единого трекера, а оценки требовали строгой приватности (каждый видит только свои, директор — все).
Задача — поднять собственный (self-hosted) issue-трекер, не зависящий от облачных PM-сервисов, перенести в него накопленную историю из Telegram и адаптировать интерфейс под русскоязычную команду. Выбран open-source Plane Community Edition v1.3.0 (AGPL-3.0). Проект самоинициированный — инфраструктура и инструментарий для собственной команды (pet/internal).
Что я сделал
Роль — devops/инженер-интегратор поверх готового продукта, не разработка самого Plane. Три направления работ:
- Деплой и эксплуатация. С нуля поднял продуктовый инстанс Plane CE v1.3.0 на VDS (Ubuntu 24.04, 2 vCPU / 4 GB RAM, провайдер aeza, Москва): базовый hardening, Docker, внешний Caddy-reverse-proxy с TLS, бэкапы, restore-runbook, аварийный rollback-план.
- Миграция данных. Спроектировал и реализовал на Python идемпотентный пайплайн Telegram → Plane через REST API: парсинг JSON-выгрузок, LLM-классификация сообщений, маппинг в проекты/задачи/комментарии/страницы, отдельная приватная изоляция оценок. Реально импортировано 4 проекта, 198 задач, 357 комментариев (подтверждается state-журналом и логом запросов).
- Локализация. Провёл аудит и доработку русского перевода Plane: терминологический глоссарий, стайл-гайд, бенчмарк российских PM-инструментов, патчи локалей и кода frontend, deploy-runbook кастомной сборки образа.
Честно о границах: продукт Plane — сторонний open-source; я его разворачивал, кастомизировал локализацию и наполнял данными, но не разрабатывал его кодовую базу. Доработка локализации подготовлена и доведена до deploy-ready состояния; финальная выкатка кастомного образа в прод и visual-QA по артефактам не подтверждены.
Решение и подход
Инфраструктура. Стек 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.
Результат
- Инстанс развёрнут и работал в проде: снимок 2026-05-04 показывает 12 контейнеров стека в статусе «Up 6 days (healthy)» — деплой ~28.04.2026 (источник:
i18n/reports/snapshot-baseline.md). - Миграция выполнена: 588 сущностей в state-журнале — 4 проекта, 12 участников, 7 меток, 198 задач, 357 комментариев, 9 страниц, 1 финализированный приватный проект; лог запросов содержит ответы
201 Createdи успешныйpsql UPDATE 1при установке приватности (источники:migration/output/import-state.jsonl,import.log). - Локализация доработана (этапы аудит→глоссарий→правки): 223 пропатченных файла, 234 авто-переведённые фразы, рост
ru/translations.ts95KB→143KB (источник:i18n/README.md). Выкатка кастомного образа в прод по артефактам не подтверждена — отмечено в открытых вопросах. - Подтверждённого внешнего метрик-эффекта (нагрузка, число активных пользователей в динамике) в артефактах нет — не зафиксировано (уточнить у автора).
Стек и обоснование
- 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. - OpenRouter (Claude Sonnet 4.6 / Opus 4.7) — единый ключ к нескольким моделям, prompt caching для удешевления прохода по 500+ сообщениям, лёгкая смена модели через env.
- Next.js / TypeScript — слой локализации Plane; правки требуют пересборки frontend-образа.
- UFW / fail2ban / unattended-upgrades / swap — базовая защита и устойчивость одиночного VDS.
Роль ИИ в проекте
Проект целиком построен по doc-driven, AI-augmented схеме в Claude Code:
- research→plan→execute. Перед деплоем подготовлены 5+ исследовательских документов (
research-docker-ubuntu.md,research-proxy-ssl.md,research-plane-v1.3.0.md,research-backup-monitoring.md,research-2026-04-28.md), затем сведённыйplan-2026-04-28.md(~1750 строк, пошаговый рунбук с rollback) иpreflight-2026-04-28.md(проверка DNS/SSH/окружения до изменений). - Промпт-инжиниринг. Две объёмные role-prompt-«сессии» с режимом max-effort, явными гард-рейлами («ничего не выдумывать», аудит-трейл, идемпотентность):
prompt-telegram-to-plane-migration.md(data-engineer + Plane CE specialist) иprompt-russian-translation-improvement.md(localization engineer). Они задают контекст, privacy-требования и поэтапный план с контрольными точками. - LLM внутри продукта, а не только как ассистент. В самом пайплайне миграции ИИ используется как компонент:
scripts/llm_client.pyчерез OpenRouter вызывает Claude Sonnet 4.6 для классификации сообщений и Claude Opus 4.7 для агрегации brief'ов, сcache_control: ephemeralдля prompt caching. Результаты складываются вmanual-review.mdдля ручной доревизии — то есть human-in-the-loop поверх LLM-классификации. - В
.claude/присутствуетscheduled_tasks.lock(использование scheduled tasks Claude Code). ФайловCLAUDE.md/AGENTS.md/opencode.jsonв проекте нет — контекст держался в самих prompt-файлах и project-memory.
Инженерные вызовы
- 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
- поддержка