Перейти к содержимому
Эльдар Шахвалиев
Обсудить проект
← Все проекты
202604.2026 — presentВ продеDevOps

Self-hosted Plane CE — деплой, миграция и локализация для веб-студии

веб-студия (small team, 5 человек) · Веб-разработка / внутренний инструмент проектного менеджмента

DevOps / инженер-интегратор (деплой, миграция данных, локализация)

  • 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 + конфиги)

Контекст и задача

Веб-студия из 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. Три направления работ:

  1. Деплой и эксплуатация. С нуля поднял продуктовый инстанс Plane CE v1.3.0 на VDS (Ubuntu 24.04, 2 vCPU / 4 GB RAM, провайдер aeza, Москва): базовый hardening, Docker, внешний Caddy-reverse-proxy с TLS, бэкапы, restore-runbook, аварийный rollback-план.
  2. Миграция данных. Спроектировал и реализовал на Python идемпотентный пайплайн Telegram → Plane через REST API: парсинг JSON-выгрузок, LLM-классификация сообщений, маппинг в проекты/задачи/комментарии/страницы, отдельная приватная изоляция оценок. Реально импортировано 4 проекта, 198 задач, 357 комментариев (подтверждается state-журналом и логом запросов).
  3. Локализация. Провёл аудит и доработку русского перевода 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.ts 95KB→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
  • поддержка