# План реализации: SelfPost **Статус:** черновик для согласования (см. ТЗ, раздел 12, п. 7 — план утверждается до начала кодирования). **Основа:** [specification.md](specification.md) v1.0. Принцип движения (ТЗ 12.4): сначала минимальный работающий скелет (образ собирается, три процесса стартуют, пустая панель с логином), затем итеративное наращивание. Требования безопасности раздела 7.6 закладываются сразу в соответствующих эндпоинтах, а не «потом» (ТЗ 12.5). Коммиты — только по явной команде (ТЗ 12.1). --- ## Зафиксированные технические решения (до кодирования) Эти выборы предлагается утвердить вместе с планом — они влияют на всю структуру. | Решение | Выбор | Обоснование | |---|---|---| | Module path | `codeberg.org/mix/selfpost` | основной репозиторий — Codeberg (ТЗ 11.7) | | Базовый образ | `debian:bookworm-slim` | зафиксировано ТЗ 4 | | SQLite-драйвер | `modernc.org/sqlite` (чистый Go, BSD-3) | сохраняет статический бинарник без cgo; лицензия permissive (ТЗ 7.1, 12.8) | | bcrypt | `golang.org/x/crypto/bcrypt` (BSD) | хэш пароля админа (ТЗ 7.6.1) | | milter | `github.com/emersion/go-milter` (MIT) | единственный жизнеспособный вариант; **требует ранней проверки совместимости** (ТЗ 7.3, риск) | | Front-end | `html/template` + вендоренный HTMX, без npm/SPA | зафиксировано ТЗ 7.1 | | Версия в бинарнике | `-ldflags "-X main.version=..."` | для проверки совместимости бэкапа (ТЗ 7.5.А, 11.1) | Каждая внешняя зависимость обоснована и permissive/GPL-совместима (ТЗ 12.8). Больше сторонних зависимостей без согласования не вводим. **Предлагаемая структура репозитория:** ``` cmd/panel/ — main панели (HTTP + milter + log-tailer + rate-limit) cmd/selfpost-backup/ — CLI-утилита бэкапа (ТЗ 11.6) internal/store/ — SQLite: схема, миграции, запросы internal/domain/ — доменная модель, DKIM, OpenDKIM KeyTable/SigningTable internal/app/ — приложения, SASL (sasldb2), sender_login_maps internal/postfix/ — генерация конфигов, reload, escaping internal/milter/ — journal-milter + rate-limit level 2 internal/logtail/ — хвост mail.log, обновление статусов internal/web/ — хендлеры, сессии, setup-link, middleware internal/backup/ — полный бэкап/restore, экспорт/импорт домена web/templates/ — html/template web/static/ — вендоренный htmx.min.js build/ — Dockerfile, supervisord.conf, стартовые скрипты, шаблоны конфигов, logrotate deploy/ — docker-compose.yml (Apache) + фрагменты nginx/Caddy/Traefik ``` Тестовый стенд: `ssh root@selfpost.mixfed.ru` (PTR настроен, порт 25 предполагается открытым) — [dev/env.txt](../dev/env.txt). Секреты разработки — только в `dev/` (в `.gitignore`). --- ## Фаза 0 — Каркас проекта и де-риск milter'а **Цель:** структура готова, критический риск проверен до того, как в него вложено много кода. - `go mod init`, структура каталогов, пустые пакеты, `main.version`. - `LICENSE` — полный текст AGPL-3.0 (ТЗ 11.9). - Скелет `README.md`, Makefile/скрипты сборки с ldflags-версией. - **Спайк по milter (де-риск, ТЗ 7.3):** на тестовом сервере поднять голый Postfix из Debian bookworm + минимальный go-milter, убедиться, что версия протокола совместима и что milter читает `From`/`To`/`Subject`/SASL-login на этапе EOH. Проверить поведение `milter_default_action` при падении/таймауте milter'а (fail-open). Результат спайка — краткая заметка в `dev/`. **Готово, когда:** `go build`/`go vet` проходят на пустом каркасе; спайк подтвердил работоспособность go-milter с Postfix из образа (или зафиксировал альтернативу). --- ## Фаза 1 — Образ + supervisord + три процесса (минимальный скелет) **Цель:** `docker build` собирает образ, контейнер стартует, три процесса живы, пустая панель отвечает на `:8080`. - `Dockerfile` (bookworm-slim): postfix, opendkim, cyrus-sasl (`sasldb2`, `sasl2-bin`), supervisor, logrotate, ca-certificates; сборка Go-бинарников многостадийно. - `supervisord.conf` с `priority=`: OpenDKIM → panel → (обёртка) Postfix; при неперезапускаемом падении любого процесса контейнер завершается (ТЗ 4). - **Стартовая обёртка Postfix**: опрос готовности milter-сокетов OpenDKIM и journal-milter (`test -S`, интервал, таймаут ~30с), запуск `postfix start-fg` только после готовности обоих; при таймауте — выход с ошибкой (ТЗ 4). - Панель: HTTP `:8080` с заглушкой, **stub-listener journal-milter** (создаёт сокет, чтобы обёртка проходила), stub log-tailer. - Непривилегированный пользователь для панели (ТЗ 7.6.8). **Готово, когда:** образ собирается, `docker run` даёт три живых процесса, панель отдаёт заглушку, обёртка корректно ждёт сокеты. --- ## Фаза 2 — Персистентность, инициализация админа, вход **Цель:** безопасный вход в панель одного администратора. - SQLite-схема + миграции: домены, приложения, админ, `send_log`, лимиты, настройки, флаг «настройка завершена»/setup-токен (ТЗ 9). Единый корень `/data` (ТЗ 7.5.А, 9). - **Setup secret-link (ТЗ 7.6.1):** токен ≥128 бит из `crypto/rand`; вывод в stdout + `/data/setup-token`; TTL 10 мин с перегенерацией; rate-limit маршрута; `subtle.ConstantTimeCompare`; неудачи НЕ инвалидируют токен; одноразовая форма создания админа; инвалидация навсегда после успеха (маршрут → 404). - bcrypt-хэш пароля админа; никакого plaintext. - Логин, сессии (крипто-случайный токен, cookie `HttpOnly`/`Secure`/`SameSite`), rate-limit логина (ТЗ 7.6.5–6). - Базовый layout `html/template`, вендоренный HTMX, middleware аутентификации. **Готово, когда:** первый запуск печатает setup-ссылку, админ создаётся один раз, повторный `/setup` → 404, вход/выход работают, токены сессий безопасны. --- ## Фаза 3 — Домены + OpenDKIM **Цель:** добавление/удаление домена с генерацией DKIM. - Добавить/список/удалить домен; при удалении — предупреждение о каскадном удалении приложений (ТЗ 7.2.4). - Генерация DKIM-ключа + селектор per-domain (дефолт из `DKIM_SELECTOR_DEFAULT`); ключи в `/data/...` переживают рестарт (ТЗ 6, 9). - Поддержка `KeyTable`/`SigningTable`, reload OpenDKIM. - Показ DKIM TXT-записи per-domain (ТЗ 7.2.10). - **Безопасная запись конфигов** с санитизацией/экранированием (ТЗ 7.6.4); `os/exec` без shell и без интерполяции ввода (ТЗ 7.6.3); строгая валидация имени домена (whitelist символов, ТЗ 7.6.2). **Готово, когда:** домен добавляется/удаляется, DKIM-ключ генерируется и переживает рестарт, TXT-запись показывается, OpenDKIM перечитывает таблицы. --- ## Фаза 4 — Приложения + SASL + привязка к домену **Цель:** ядро доменной модели (ТЗ 4.1, 5.1). - Создание учётки в `sasldb2` (эквивалент `saslpasswd2`), генерация сильного пароля, показ **один раз** (ТЗ 7.6.1). - Режим адресов на уровне приложения: «любой адрес домена» (wildcard `@example.com`) либо «список» (запись на каждый адрес); серверная валидация принадлежности каждого адреса домену приложения (ТЗ 7.6.2). - Поддержка `smtpd_sender_login_maps` соответственно режиму; many-to-one. - Список приложений домена с текущим режимом; редактирование режима; удаление приложения (только его записи); перевыпуск пароля (режим сохраняется) — с `postfix reload` (ТЗ 7.2.5–9). **Готово, когда:** приложения создаются/редактируются/удаляются, карта привязки корректно пересобирается, пароль показывается один раз. --- ## Фаза 5 — Полная конфигурация Postfix (исходящий релей) **Цель:** реальная отправка почты с аутентификацией и DKIM. - `master.cf`: `smtps` 465 (wrapper TLS, `smtpd_tls_wrappermode=yes`) как основной; `submission` 587 (STARTTLS, `smtpd_tls_auth_only=yes`) — опционально, тем же способом (ТЗ 5, 5.1). - SASL (`smtpd_sasl_auth_enable`), TLS cert/key из `TLS_CERT_FILE`/`TLS_KEY_FILE` (read-only mount), периодический `postfix reload` для обновления сертификата (ТЗ 5.2). - `smtpd_recipient_restrictions` (`permit_sasl_authenticated`, `reject_unauth_destination`), `smtpd_sender_restrictions` + `reject_sender_login_mismatch`; **никакого open relay** (ТЗ 5.4, 5.1.3). - Исходящая доставка напрямую (MX-lookup, `smtp_tls_security_level=may`). - Уровень 1 rate-limit (`anvil`: `smtpd_client_message_rate_limit`, `anvil_rate_time_unit`) из env (ТЗ 5.5, 7.4). - Milter-цепочка `smtpd_milters`: OpenDKIM + journal-milter; `milter_default_action` для journal — **fail-open** (ТЗ 7.3). **Готово, когда:** с тестового сервера письмо реально уходит получателю, подписано DKIM, аутентификация обязательна, привязка отправителя работает, неаутентифицированный релей отклоняется. --- ## Фаза 6 — Journal-milter и обновление статусов (Send Log, бэкенд) **Цель:** структурированный журнал отправки. **Самый чувствительный узел (ТЗ 7.3) — повышенное внимание к тестам.** - Реализация journal-milter на go-milter: на EOH читает домен, SASL-login, From, To, Subject; создаёт запись `send_log` со статусом «в очереди», привязка к queue-id. - Отдельная запись на пару (queue-id, получатель) (ТЗ 7.3.3). - Log-tailer: горутина хвостит `mail.log`, парсит `sent`/`bounced`/`deferred` по queue-id, обновляет записи. - Retention: фоновая очистка старше `SEND_LOG_RETENTION_DAYS` (дефолт 90). - **Тесты отказа:** поведение при падении/таймауте milter'а — приём почты не блокируется (fail-open проверяется явно). **Готово, когда:** отправленное письмо порождает запись, статус доходит до финального, ретеншн чистит старое; убийство milter'а не роняет приём почты. --- ## Фаза 7 — UI мониторинга (журнал, очередь, лог) **Цель:** экраны наблюдения с автообновлением. - Журнал отправки: таблица (время, домен, приложение, From, To, Subject, статус), **серверные фильтры** по домену и приложению (`WHERE`), пагинация, HTMX-polling; экранирование вывода (ТЗ 7.3, 7.6.7). - Просмотр очереди (`postqueue -p` в читаемом виде), HTMX-polling (ТЗ 7.2.11). - Хвост `mail.log`, HTMX-polling (ТЗ 7.2.13). - Кнопка ручного reload (ТЗ 7.2.12). - Fragment-эндпоинты возвращают HTML-куски, не JSON (ТЗ 7.1). **Готово, когда:** три экрана работают, фильтры серверные, автообновление через polling, вывод экранирован. --- ## Фаза 8 — Дифференцированные лимиты (rate limit уровень 2) **Цель:** лимиты на домен/приложение поверх backstop уровня 1. - Настройка в панели: ожидаемые IP + лимит (N писем за окно) на уровне домена и приложения; оба необязательны; пустая IP-привязка допустима (ТЗ 7.4). - Проверка в milter: ключ — client IP; счётчик из SQLite (переиспользует данные журнала); превышение → отказ 4xx + опциональная запись «отклонено по лимиту»; **fail-open** при недоступности milter'а (ТЗ 7.4). **Готово, когда:** лимит на домен/приложение срабатывает (4xx), пустая привязка не ломает отправку, при падении milter'а остаётся уровень 1. --- ## Фаза 9 — Бэкап/восстановление и экспорт/импорт домена **Цель:** переезд «прост как архив» + перенос одного домена. - **Полный бэкап (ТЗ 7.5.А):** архив всего `/data` (SQLite через `VACUUM INTO`/Backup API для консистентного снимка на живом контейнере, DKIM-ключи, `sasldb2`, `manifest.json` с версией). Без TLS-сертификатов и очереди Postfix. - Кнопка в панели + **CLI `selfpost-backup`** через `docker exec` (ТЗ 11.6). - **Restore:** сверка версии манифеста с версией бинарника; при несовпадении — отказ с понятным сообщением (какой тег образа нужен); состояние регенерируется из SQLite тем же путём, что при обычном старте, без отдельной ветки restore (ТЗ 7.5.А). - **Экспорт/импорт домена (ТЗ 7.5.Б):** файл с именем домена, DKIM-ключом/селектором, приложениями и их записями `sasldb2` (рабочие пароли без перевыпуска); импорт восстанавливает домен без смены DNS. Файл помечается как секрет. **Готово, когда:** бэкап скачивается (панель и CLI), restore на чистом контейнере той же версии поднимает всё без пере-настройки; несовпадение версии отклоняется; экспорт/импорт домена переносит рабочие креды. --- ## Фаза 10 — Деплой и документация **Цель:** поставка и эксплуатационная документация. - `docker-compose.yml`: **Apache** как основной reverse-proxy «из коробки», **фиксированный тег версии** (не `:latest`), bind mount `./data:/data` + read-only mount сертификатов, hardening (non-root, `cap_drop`, ограничение ФС) (ТЗ 10, 9). - Альтернативные фрагменты: nginx, Caddy (проверить актуальный путь хранилища), Traefik (шаг извлечения PEM) (ТЗ 10.3). - `logrotate` для `mail.log` в образе (ежедневно, 7–14 файлов, ТЗ 9). - `README.md`: требования к площадке (чеклист), настройка DNS per-domain (SPF/DKIM/DMARC + PTR), прогрев IP, процедуры бэкапа/restore и экспорта/импорта (оба файла — секреты), фиксированный тег, требования к машине, лицензия AGPL-3.0 и её смысл, ссылки Codeberg (основной)/GitHub (зеркало) (ТЗ 10, 11.7). **Готово, когда:** `docker compose up` за Apache поднимает рабочий стенд; README покрывает установку, DNS, бэкап/миграцию, лицензию. --- ## Фаза 11 — Финальный проход по безопасности и приёмка **Цель:** соответствие разделу 7.6 и критериям ТЗ 12. - Аудит: панель не под root и права ФС; серверная валидация ввода; `os/exec` без shell; экранирование всего вывода из логов/очереди; санитизация записи в конфиги; флаги cookie; rate-limits (setup, логин). - `go build`, `go vet`, `go test`; `docker build`; проверка старта контейнера (ТЗ 12.2–3). - Опционально: `/security-review` по диффу ветки. **Готово, когда:** все пункты 7.6 подтверждены, сборка/вет/тесты/образ зелёные, контейнер стартует чисто. --- ## Опциональные фазы — целевой релиз 2.x.x (вне базового объёма v1.0) Эти фазы **не входят** в линейный базис 0→11 и не являются частью поставки v1.0 (v1.x — только исходящий релей). Они отнесены к **релизной линии 2.x.x** и добавлены в дорожную карту как согласуемые расширения. **Реализация — только после явного согласования (ТЗ 12.6):** ТЗ v1.0 раздел 3 явно исключает приём входящей почты из объёма, поэтому включение этой функциональности — сознательное расширение границ проекта (major-релиз 2.0), а не доработка по своей инициативе. Внесение в план фиксирует намерение и дизайн; кодирование начинается отдельным решением. ### Фаза O1 (→ 2.x.x) — Входящий релей (backup-MX / пересылка) — опция/плагин **Цель:** возможность принимать почту на порт 25 для явно настроенных доменов и пересылать её на заданный вышестоящий backend (роль backup-MX / relay-forwarder), **как выключаемый по умолчанию модуль**, не затрагивающий поведение и поверхность атаки базового исходящего релея. **Зачем это нужно (сценарии):** - **Backup-MX** — принять почту, когда основной почтовый сервер домена временно недоступен, и передать её, когда он вернётся. - **Фронт для сервера без внешнего IP** — у оператора есть свой почтовый сервер, который по каким-то причинам **сам не может принимать почту из интернета** (нет статического/внешнего IP, за NAT, серый адрес, закрытый порт 25 на входящую и т.п.). SelfPost с публичным IP и корректным PTR выступает публичным входным узлом для домена (MX указывает на него) и пересылает почту на этот внутренний/недоступный извне сервер. **Граница объёма (критично — что это НЕ):** - **ЭТО:** приём на 25 для доменов из явного списка + пересылка (relay/forward) на upstream (`relay_domains` + `transport_maps` + `relay_recipient_maps`). Postfix здесь — чистый пересыльщик, без локальной доставки. - **ЭТО НЕ (остаётся out of scope, ТЗ 3):** локальная доставка в почтовые ящики, IMAP/POP3, webmail, Dovecot. Никаких mailbox'ов. SelfPost также **не реализует и не тянет в свой образ** движок антиспама/антивируса (rspamd/ClamAV) — но, в отличие от прежней формулировки, и **не** перекладывает фильтрацию на backend (см. блок «Антиспам» ниже): предоставляет точку подключения внешнего фильтра. **Почему как опция/плагин:** - Приём на порт 25 меняет модель угроз (open relay для входящей, backscatter, spam-ingress). Поэтому по умолчанию **выключено** флагом env `INBOUND_RELAY_ENABLE=false`; включение — осознанный шаг оператора. - Изоляция: отдельные таблицы SQLite, отдельные хендлеры/страницы панели, отдельная ветка генерации конфига. При выключенном флаге входной listener, таблицы и UI отсутствуют — базовый исходящий тракт байт-в-байт неизменен. **Что делать:** - Env-флаг `INBOUND_RELAY_ENABLE` (default false); при `true` — генерировать входной сервис и его конфиг из состояния панели тем же путём, что остальной конфиг (`postfix-config.sh`). - **`master.cf`:** входной `smtp inet` на 25 для приёма из интернета (сейчас 25 используется только на исходящую доставку). Отдельный от 465/587: на 25 **не** предлагается SASL и **не** разрешается отправка наружу — только приём для `relay_domains`. - **Анти-open-relay для входящей (обязательно):** `smtpd_relay_restrictions`/`smtpd_recipient_restrictions` входного smtpd принимают почту **только** для доменов из `relay_domains` и **только** для известных получателей (`relay_recipient_maps`); всё прочее — `reject_unauth_destination`/`reject_unlisted_recipient`. Открытый релей и приём «для кого угодно» невозможны. - **Backscatter:** предпочтительно знать валидных получателей (reject unknown recipient на этапе RCPT), чтобы не порождать bounce на несуществующие адреса. - **Панель управляет:** список входящих доменов; для каждого — upstream destination (`host:port`, транспорт), опциональный список валидных получателей, опциональный TLS к upstream. Строгая валидация домена/хоста/порта (whitelist), injection-safe запись map-файлов (как `sender_login_maps` в Фазе 4), `os/exec` без shell (ТЗ 7.6.2–4). - **Милтеры:** OpenDKIM на входящем тракте не нужен (чужую входящую не подписываем). journal-milter опционально переиспользовать для журнала входящих (доп. работа) либо на первом этапе оставить входящий без него; поведение fail-open сохраняется. - **Rate-limit/размер:** грубый лимит по client IP (`anvil`, как L1) и `message_size_limit` на входном smtpd. **Антиспам (важная, но опциональная возможность).** Это ценная опция, но она **не обязательна**: часть операторов вполне устроит **слепая пересылка без фильтрации** — например, когда backend сам умеет фильтровать по содержимому, стоит доверенный upstream, или объём/риск невелик. Поэтому антиспам-хук по умолчанию **выключен** (пустой `INBOUND_ANTISPAM_MILTER`), и входящий релей полностью работоспособен без него. Важно другое — где фильтрация возможна технически: при «слепом» relay целевой backend видит подключающимся IP адрес **SelfPost**, а не исходного отправителя, поэтому на backend'е ломается всё, что завязано на origin IP (DNSBL/репутация проверяются против IP SelfPost, SPF даёт fail — SelfPost не входит в SPF домена-отправителя). **Единственная точка, где ещё виден настоящий client IP — входной хоп на SelfPost**; поэтому тем, кому фильтрация нужна, она должна быть *подключаема именно здесь*, а не переложена на backend, который эту информацию уже потерял. Дизайн подключения: - **Движок антиспама — отдельный опциональный контейнер** (rspamd и т.п.), который оператор запускает **только если нужна эта опция** (тот же принцип, что reverse-proxy — отдельный контейнер вне образа SelfPost). SelfPost его **не содержит и не запускает** — образ и принцип «один контейнер, три процесса» неизменны, ТЗ 3 не нарушается (SelfPost не реализует антиспам). - **SelfPost предоставляет точку подключения:** milter-хук на входном smtpd. Адрес движка задаётся env (например, `INBOUND_ANTISPAM_MILTER=inet:antispam:11332`, пусто → хук выключен) и добавляется в `smtpd_milters` **только входного** тракта (не на 465/587). Postfix передаёт milter'у настоящий client IP/HELO/PTR — фильтр видит истинный origin. `milter_default_action` для этого milter'а — конфигурируемый (fail-open vs tempfail); дефолт определить при реализации. - **Нативный backstop без зависимостей:** на том же входном хопе доступны средства Postfix по origin IP — `reject_rbl_client` (DNSBL), проверки HELO/PTR — работают даже без внешнего контейнера. Плюс сохранение аутентификации для downstream через ARC/`Received` там, где часть фильтрации всё же остаётся на backend. - **docker-compose:** задокументировать опциональный фрагмент antispam-сайдкара (как альтернативные фрагменты reverse-proxy) — контейнер поднимается вместе со стеком только при включённой опции. - **Персистентность:** новые таблицы и map-файлы под `/data` — попадают в полный бэкап автоматически (Фаза 9). Экспорт/импорт домена можно расширить входящей конфигурацией — опционально, пометить. - **DNS-документация:** для входящего домена нужна `MX`-запись, указывающая на сервер (в отличие от исходящего, где MX не требуется) — отразить в разделе DNS README. **Безопасность (ТЗ 7.6 распространяется полностью):** валидация ввода на сервере, экранирование записи в конфиги, `exec` без интерполяции, никакого open relay, защита от backscatter. **Готово, когда:** при `INBOUND_RELAY_ENABLE=true` и настроенном домене письмо на порт 25 для этого домена пересылается на заданный upstream; почта для ненастроенных доменов/получателей отклоняется (не open relay, не backscatter); при заданном `INBOUND_ANTISPAM_MILTER` входящая проходит через внешний фильтр с настоящим origin IP (проверено сайдкар-контейнером), при пустом — хук не мешает; при `INBOUND_RELAY_ENABLE=false` — входной порт/таблицы/UI отсутствуют, базовый исходящий релей неизменён; `build`/`vet`/`test`/образ зелёные. **Риски:** open relay/backscatter (снимается `relay_domains` + `relay_recipient_maps` + `reject_unauth_destination`); потеря origin IP для фильтрации на backend'е при пересылке (снимается milter-хуком антиспама + нативным DNSBL на входном хопе, где origin IP ещё виден); порт 25 на приём расширяет поверхность атаки (по умолчанию выключено). **Модель:** Opus (инфра/безопасность, риск open relay). **Внешняя зависимость деплоя:** опциональный antispam-контейнер — вне образа SelfPost, поднимается оператором при включении опции. --- ## Ключевые риски и как их снимаем 1. **Go-milter (ТЗ 7.3)** — самый рискованный узел (баг ломает не только журнал, а сам релей). Снимается ранним спайком (Фаза 0) и явными тестами отказа (Фаза 6), fail-open через `milter_default_action`. 2. **Холодный старт milter-сокетов** — обёртка Postfix ждёт готовности сокетов (Фаза 1). 3. **Версионирование бэкапа** — фиксированный тег образа + проверка манифеста (Фазы 9–10). 4. **Открытый релей / утечка кред** — привязка `sender_login_maps` + `reject_sender_login_mismatch`, обязательный SASL, отсутствие `mynetworks`-авторизации (Фаза 5). ## Порядок и зависимости Фазы линейны по зависимостям (0→1→2→3→4→5→6→7→8→9→10→11), кроме спайка milter (0), который де-рискует Фазу 6 заранее. После каждой фазы — работающий инкремент, пригодный для проверки на тестовом сервере. Опциональная **Фаза O1 (входящий релей)** — вне этого линейного базиса: не является частью v1.0, зависит только от готового исходящего тракта (Фазы 1–5) и требует отдельного согласования (ТЗ 12.6, расширение за пределы раздела 3) до кодирования.