Skip to content

Latest commit

 

History

History
961 lines (747 loc) · 64.2 KB

docker-guide.md

File metadata and controls

961 lines (747 loc) · 64.2 KB

Запуск в Docker: руководство для администратора

Это руководство про контейнеры. Если вы ставите инструмент прямо на машину с 1С (обычный путь для Windows), вам нужно другое: пошаговое руководство по установке.

Здесь всё то же самое, но программа живёт в контейнере, а машина с Docker может быть отдельным сервером Linux.


0. Главное, что надо понять до начала

Два разных образа, и это не прихоть

Образ Что внутри Можно ли публиковать
ядро (core) Python, Git, сам инструмент да: ничего чужого внутри нет
с платформой (native) ядро + толстый клиент 1С:Предприятие нет, никогда

Платформа 1С:Предприятие распространяется по лицензии и её нельзя класть в образ, который увидит кто-то ещё. Поэтому образ с платформой вы собираете у себя, из своего легально полученного дистрибутива, и он остаётся только на вашей машине. Ни в какой реестр его выкладывать нельзя.

Что проверено, а что нет — читать обязательно

Актуальная native-приёмка: VERIFIED_STATUS.md и TCP-проверка 20.09.2026. Числа ранних автотестов ниже относятся к прежнему этапу. Compose проверялся в прежнем сценарии ядра/фикстуры; для native используйте launcher из OPERATIONS_RU.md, а не этот Compose.

Состояние
Сборка и запуск образа ядра на Linux проверено
Все автотесты внутри Linux-контейнера проверено, 321 прошли, 19 пропущены (им нужен Windows: NTFS junction, описатель каталога без FILE_SHARE_DELETE и обёртка на PowerShell)
Сценарии пользователя на файловой фикстуре в контейнере проверено, 45 проверок
Разделение доступа к хранилищу между двумя контейнерами проверено на подменённом конфигураторе (см. §9)
Запуск docker compose и отправка в Git проверено, включая отправку по ssh с проверкой ключа сервера
Сборка приватного образа из настоящего комплекта 8.3.27.2130 проверено, платформа ставится и запускается
Какие системные пакеты нужны платформе проверено — список снят ldd с установленного 1cv8, а не из документации
Нужен ли Xvfb проверено: нужен. Без DISPLAY конфигуратор не стартует вовсе (код 255)
Остановка контейнера (docker stop) доходит до работы проверено после исправления: xvfb-run сигнал не пропускал
Произвольный UID для образа с платформой проверено: нужна учётная запись. Без записи в /etc/passwd толстый клиент падает по SIGSEGV; образ дописывает её на старте (§10)
Причина отказа конфигуратора видна вызывающему проверено на живой платформе: текст из файла /Out попадает в ошибку (§10)
Работа с настоящим хранилищем 1С из контейнера Linux ПРОВЕРЕНО на стенде, файловый и TCP-доступ, 17/17 версий при jobs=1/2; отчёт
Лицензирование платформы в контейнере Действующая лицензия обеспечила реальные выгрузки на стенде; универсальная настройка для чужой среды и первопричина редких отказов не доказаны (§10, границы)

На раннем этапе стенд останавливался с ошибкой «Не найдена лицензия». Это исторический результат, а не текущий статус: после подключения действующего лицензирования выполнены реальные файловые и TCP-выгрузки. Приёмка своей среды всё равно обязательна; инструкции ниже не выдают и не заменяют лицензию.

Чем контейнер отличается от обычной установки

Ничем по сути: те же команды, тот же манифест, тот же результат в Git. Разница только в том, что пути внутри контейнера свои, и их надо правильно связать с папками на вашей машине. Этим и занято всё руководство.


1. Что должно быть на машине

  • Docker — на Linux это docker и docker compose; на Windows — Docker Desktop с включённым линуксовым движком.
  • Место на диске — считайте не меньше 40 ГБ там, где Docker хранит данные. На Windows это виртуальный диск Docker Desktop, а не папка проекта.
  • Дистрибутив 1С для Linux — только для образа с платформой (§10). Для всего остального не нужен.

Проверить, что Docker жив:

docker version
docker compose version

2. Папки: что где лежит

Заведите отдельную папку для работы инструмента. Внутри репозитория ничего из этого лежать не должно.

/srv/gitsync/                     ← любое место, какое вам удобно
├── база/                         ← ВЫХОДНОЙ репозиторий Git (сюда пишется история)
├── конфигурация/
│   └── storages.json             ← манифест: какие хранилища выгружать
└── секреты/
    └── storage.txt               ← файл с паролем хранилища

Три вещи, которые чаще всего путают:

  1. Исходный репозиторий и выходной — разные вещи. Исходное — это хранилище конфигурации 1С (сервер хранилища или папка), и инструмент его только читает. Выходное — это репозиторий Git, куда ложится история. Их нельзя совмещать.
  2. Манифест лежит ВНЕ выходного репозитория. Иначе он попадёт в историю и будет мешать: инструмент не даёт класть служебные файлы в рабочую копию.
  3. Пароль лежит в отдельном файле, вне репозитория. Ни в манифесте, ни в командной строке (там его видно в списке процессов), ни в образе.

Как эти папки видны внутри контейнера

Контейнер видит не ваши пути, а свои. Соответствие всегда одно и то же:

Папка на машине Путь внутри контейнера Права
/srv/gitsync/база /repo запись
/srv/gitsync/конфигурация /config только чтение
/srv/gitsync/секреты/storage.txt /run/secrets/storage_password только чтение
(том Docker) /work/tmp запись, прочие временные файлы
(том Docker) /var/lib/gitsync/sessions запись, общие блокировки
не монтируется /var/lib/gitsync/scratch КАТАЛОГ ЗАПУСКА: только слой контейнера

В манифесте пишутся пути КОНТЕЙНЕРА, то есть /repo, /config, а не /srv/gitsync/база и не D:\.... Это самая частая ошибка.

Каталог запуска не монтируется — это проверяется и нарушение отвергается. Выгрузка конфигуратора складывается в /var/lib/gitsync/scratch, и этот путь обязан оставаться на собственном слое контейнера. Замерено на стенде (evidence/stage-18): если каталог запуска лежит на смонтированном томе — bind-mount каталога хоста ИЛИ том Docker, именованный либо анонимный, — то другой контейнер с тем же UID видит выгрузку, читает её и уничтожает rm -rf. На собственном слое контейнера тот же нападающий каталога не видит вовсе. Поэтому temp_root в манифесте контейнера задавать не нужно: умолчание уже верное. Если задать его на смонтированный том, задание откажется работать — до создания каталога и до любого удаления.

Если Docker у вас на Windows (Docker Desktop)

Слева пишутся пути Windows, справа — всё равно линуксовые:

REPO_HOST=D:/git/база          →  /repo
CONFIG_HOST=D:/gitsync/конфиг  →  /config

Косые чёрточки — обычные /, даже для Windows-путей. Обратные слэши в файле .env работают ненадёжно. И ещё раз: в манифесте D:/... не пишут никогда — только /repo.


3. Кто запускает контейнер: UID и GID

Контейнер работает не от администратора, а от обычного пользователя. Чтобы он мог писать в ваши папки, надо запускать его своим UID. Запуск от root инструмент прекращает сам (см. §8): под root файлы в ваших папках достались бы root, и следующий обычный запуск уже не смог бы их переписать.

Узнайте свой номер:

id -u        # например 1000

Штатная форма запуска — свой UID и группа 0:

docker run --rm --user "$(id -u):0" ... gitsync-py:core --version

Группа 0 здесь — не «права root». Внутри образа рабочие каталоги (/home/app, /work/tmp, /var/lib/gitsync/sessions, /var/lib/gitsync/scratch, /repo) принадлежат app:0 и открыты на запись группе, поэтому с GID=0 работает любой UID — и тот, которого нет в /etc/passwd образа. Прав администратора это не даёт: контейнер запускается с cap_drop: ALL и no-new-privileges.

Единственное исключение — отправка в Git по ssh: OpenSSH требует, чтобы UID был описан в /etc/passwd образа. Подробности и два выхода — в §7, во врезке про No user exists for uid.

Хотите запускаться именно своей группой (--user со своим GID) — соберите образ под неё, тогда владельцем каталогов внутри образа станет ваша пара:

docker build -f docker/Dockerfile.core --target core \
  --build-arg APP_UID=$(id -u) --build-arg APP_GID=$(id -g) -t gitsync-py:core .

Оба пути равноправны. Первый не требует своей сборки образа, второй — своего GID. Чего делать не надо — запускать с чужим GID без пересборки: тогда контейнер не сможет писать в /home/app, и предполётная проверка остановит работу с кодом 78 ещё до обращения к хранилищу.

Подготовка томов (ничего чужого не трогаем)

docker volume create gitsync-sessions

Новый именованный том при первом подключении получает владельца и права от каталога образа — здесь это app:0 с правом записи для группы. Поэтому при запуске с GID=0 том готов сразу: менять владельца не нужно, рекурсивный chown чужих данных нигде не требуется.

Проверить, что связка «UID + тома» рабочая, можно до всякой синхронизации:

docker run --rm --user "$(id -u):0" \
  -v gitsync-sessions:/var/lib/gitsync/sessions \
  gitsync-py:core --version

Ответил номер версии — предполётная проверка пройдена. Ответил 78 — в сообщении написано, что именно не так и какой из двух путей выбрать.

Уже созданный том владельца не меняет. Если вы переезжаете на другой UID/GID (например, пересобрали образ под свой GID), старый том блокировок останется за прежним владельцем, и предполётная проверка честно откажет. Заведите новый том под новую пару и укажите его в SESSIONS_VOLUME:

docker volume create gitsync-sessions-1000

Менять владельца существующего тома тоже можно, но это уже действие над чужими данными — делайте его осознанно и когда синхронизация не работает.

Ваши собственные папки (рабочая копия Git, настройки, хранилище) остаются вашими: их владельца инструмент не меняет никогда. Если рабочая копия принадлежит другому пользователю, Git откажется с ней работать — разбор в §8.

Это важнее, чем кажется. Все контейнеры, которые работают с одним и тем же хранилищем, обязаны запускаться под одним и тем же UID. Файл блокировки, который не даёт двум конфигураторам зайти в хранилище одновременно, создаётся с правами «только владелец». Под другим UID его не открыть — и такой запуск не «проскочит мимо блокировки», а остановится с ошибкой доступа к файлу блокировки (проверено запуском). Данные это не портит, но половина запусков будет просто падать, поэтому единый UID — не формальность.


4. Собрать образ ядра

Из корня проекта:

docker build -f docker/Dockerfile.core --target core -t gitsync-py:core .

Проверить, что собралось то, что нужно:

# должен ответить номер версии
docker run --rm gitsync-py:core --version

# должен быть обычный пользователь, а не root
docker run --rm --entrypoint id gitsync-py:core

Базовый образ закреплён по digest (python:3.11-slim-bookworm@sha256:5282…bdf84). Это сделано специально: тег со временем переезжает на новую сборку, и без digest завтрашний образ отличался бы от сегодняшнего. Проверенная сборка — Python 3.11.16, Debian 12, Git 2.39.5.

Занимает образ 342 МБ, но 251 МБ из них — общий базовый образ Python, который переиспользуется и другими образами; собственный вклад — около 91 МБ (из них примерно 9 МБ — клиент ssh, нужный для отправки в Git).

Что лежит внутри образа (и чего там нет)

В образе выполнения нет: тестов, истории Git, документации, примеров, никаких дистрибутивов и никаких паролей. Проверить самому:

docker run --rm --entrypoint sh gitsync-py:core -c \
  'find / -xdev \( -name "test_*.py" -o -name "*.deb" -o -name "*.lic" \) | head'

Пусто — значит всё в порядке.

Что не попадает даже в контекст сборки

Файл .dockerignore не пускает в сборку историю Git, виртуальные окружения, кэши, журналы, а также всё, похожее на секрет или на дистрибутив: .env, *.key, *.pem, *.lic, *.deb, *.run, *.exe, *.cf. Docker отправляет демону всю папку сборки целиком, поэтому случайно положенный рядом файл с паролем уехал бы вместе с ней — даже если ни одна команда его не копирует.

Единственное исключение из запрета на *.cfe — src/gitsync/data/tempExtension.cfe. Это пустой шаблон расширения из оригинального gitsync под лицензией MPL-2.0 (происхождение и контрольная сумма — в src/gitsync/data/README.md), он нужен самой программе и ничьей выгрузкой не является.


5. Репетиция: прогон без 1С

Прежде чем связываться с настоящим хранилищем, стоит убедиться, что папки, права и манифест настроены верно. Для этого есть учебное «хранилище» — обычная папка с заранее подготовленной выгрузкой (--backend fixture).

Подготовьте:

mkdir -p /srv/gitsync/{база,конфигурация,фикстура}
git -C /srv/gitsync/база init -b main
cp -r examples/fixture-storage/* /srv/gitsync/фикстура/
chown -R "$(id -u)" /srv/gitsync        # свои же папки; чужих каталогов это не касается

Манифест /srv/gitsync/конфигурация/storages-fixture.json — обратите внимание, пути внутренние:

{
  "repository": "/repo",
  "defaults": {
    "backend": "fixture",
    "init": true,
    "jobs": 2,
    "email_domain": "corp.local"
  },
  "storages": [
    { "name": "Конфигурация", "subtree": "Конфигурация", "fixture_root": "/fixture" }
  ]
}

Запуск:

docker volume create gitsync-sessions

docker run --rm \
  --user "$(id -u):0" \
  -e TZ=Europe/Moscow \
  -v /srv/gitsync/база:/repo \
  -v /srv/gitsync/конфигурация:/config:ro \
  -v /srv/gitsync/фикстура:/fixture:ro \
  -v gitsync-sessions:/var/lib/gitsync/sessions \
  gitsync-py:core sync-all --config /config/storages-fixture.json

Должно получиться примерно так:

Общий репозиторий: /repo
=== Конфигурация ===
Рабочая копия подготовлена: /repo/Конфигурация
Зафиксировано версий: 2 (1..2)

Проверьте результат:

git -C /srv/gitsync/база log --format='%h %an <%ae> %ad %s' --date=iso
cat /srv/gitsync/база/Конфигурация/VERSION

Запустите ту же команду ещё раз — новых коммитов быть не должно, в ответ придёт «Новых версий нет». Это и есть правильное поведение: повторный запуск безопасен.


6. Часовой пояс — не мелочь

Хранилище 1С отдаёт время версии без указания пояса. Инструмент считает его местным. В контейнере «местное» по умолчанию — UTC, то есть время коммитов уехало бы на несколько часов относительно того, что видят разработчики, и SHA коммитов не совпали бы с прогоном на машине с 1С.

Поэтому TZ — обязательная переменная для команд sync, sync-all и clone. Без неё контейнер не начнёт работу и скажет об этом:

gitsync: предполётная проверка не пройдена.
Не задан часовой пояс (TZ).
...
Укажите пояс ИСТОЧНИКА, например: -e TZ=Europe/Moscow

Указывайте пояс источника — то есть того места, где работают разработчики 1С, а не того, где стоит сервер с Docker. Если вы осознанно хотите UTC, задайте GITSYNC_ALLOW_DEFAULT_TZ=1.

Проверено: при TZ=Europe/Moscow версия со временем 09:01:00 получает в Git дату 2026-09-16 09:01:00 +0300.


7. Запуск через compose (так удобнее)

Файлы docker/compose.yaml и docker/env.example описывают всё сразу: тома, пользователя, секреты, ограничения.

cp docker/env.example docker/.env
# отредактируйте пути и UID под свою машину

docker volume create gitsync-sessions       # один раз на машину

# проверить настройку, ничего не запуская:
docker compose --env-file docker/.env -f docker/compose.yaml config

# репетиция без 1С:
docker compose --env-file docker/.env -f docker/compose.yaml run --rm sync-fixture

Обязательные переменные заданы так, что compose откажется работать, если их забыли, и назовёт пропущенную:

required variable SOURCE_TZ is missing a value: укажите часовой пояс хранилища,
например Europe/Moscow

Том gitsync-sessions объявлен внешним намеренно. Если бы compose создавал его сам, у каждого проекта появился бы свой том с префиксом имени проекта, блокировка перестала бы быть общей — и два конфигуратора пошли бы в одно хранилище вместе. Забыли создать — будет честный отказ: external volume "gitsync-sessions" not found.

Отправка в Git — отдельная команда

Синхронизация никуда ничего не отправляет. Это сделано нарочно: у процесса, который ходит в хранилище, не должно быть ключа от вашего репозитория Git, а у процесса отправки не должно быть пароля от хранилища.

Сначала посмотрите, что получилось (коммиты, VERSION, ветка), и только потом:

docker compose --env-file docker/.env -f docker/compose.yaml run --rm push

Ключ SSH подключается только для чтения, проверка ключа сервера не отключается, пароль в адрес репозитория не встраивается. Клиент ssh входит в образ ядра — без него отправка по ssh:// падала бы с ssh: not found ещё до подключения.

UID для отправки обязан быть известен образу. OpenSSH спрашивает /etc/passwd раньше, чем делает что-либо ещё, поэтому запуск с UID, которого в образе нет, даёт No user exists for uid 1000 / fatal: Could not read from remote repository. Ключи и права тут ни при чём. Два рабочих выхода:

  • собрать образ под себя (годится и для всего остального): --build-arg APP_UID=$(id -u) --build-arg APP_GID=$(id -g), запуск GITSYNC_UID=$(id -u), GITSYNC_GID=$(id -g);
  • либо запускать отправку пользователем образа (app, uid 10001) и дать ему права на рабочую копию.

Для самой синхронизации это не нужно: без записи в /etc/passwd она работает, и контейнер об этом честно предупреждает в начале запуска.

Ключ сервера нужно получить заранее. StrictHostKeyChecking=yes означает, что неизвестный сервер будет отвергнут — это защита от подмены, а не помеха:

ssh-keyscan -t ed25519 git.example.org > /srv/gitsync/конфигурация/known_hosts
# сверьте отпечаток с тем, что публикует ваш сервер:
ssh-keygen -lf /srv/gitsync/конфигурация/known_hosts

Файл known_hosts лежит в том же каталоге настроек, который подключается как /config (только для чтения). Приватный ключ там лежать не должен: он подключается отдельно, как secret (GIT_SSH_KEY_FILE в .env), и в образ не попадает.

Проверено на локальном зеркале: после run --rm push дерево коммита в зеркале совпадает с исходным побайтово. Отправка по ssh проверена отдельно — одноразовым сервером Git по ssh в изолированной сети контейнеров, с ключом, созданным только для этой проверки (см. docs/development.md).

Если запускаете из сценария или по расписанию, добавляйте -T: docker compose ... run --rm -T sync. Без него compose выделяет терминал и забирает себе оставшийся ввод сценария — команда отработает, а остаток файла просто не выполнится. В интерактивной работе это не мешает. Имена служб — sync, sync-fixture, push; посмотреть их список: docker compose --env-file docker/.env -f docker/compose.yaml config --services.


8. Если контейнер отказался работать

Инструмент устроен так, чтобы останавливаться до работы, а не портить историю. Код возврата 78 означает «неправильно настроено». Вот все случаи.

«Git отказался работать с рабочей копией: у каталога другой владелец»

Самая частая ситуация. Папка на машине принадлежит одному пользователю, а контейнер запущен от другого — Git в такой ситуации отказывается работать (и правильно делает).

Каталог принадлежит UID 0, контейнер запущен от 10001.
Выберите одно:
  1) сменить владельца каталога на хосте: chown -R 10001:10001 <каталог на хосте>
  2) запустить контейнер владельцем каталога: --user <UID>:<GID>
  3) если владелец доверенный — разрешить ИМЕННО этот путь: -e GITSYNC_TRUST_REPO=1

Лучший вариант — первый или второй. Третий разрешает ровно один путь и ничего больше; разрешения «доверять любому каталогу» инструмент не выдаёт никогда. И учтите: разрешение снимает возражение Git, но не даёт прав на запись — если папка чужая, запись всё равно не пройдёт, и вы увидите Permission denied с точным путём.

«Каталог блокировок недоступен для записи»

Том с блокировками создан под другим UID. Либо запускайте контейнер тем же UID, либо смените владельца тома.

«Домашний каталог недоступен для записи»

Бывает при запуске с произвольным --user, когда домашняя папка не подключена. Подключите /home/app томом или задайте GITSYNC_SESSION_DIR на общий том с правом записи.

«Не удалось определить домашний каталог»

Контейнер запущен с UID, которого нет в /etc/passwd, и без HOME. Задайте GITSYNC_SESSION_DIR (абсолютным путём) — он и нужен для общей блокировки.

Остальные коды возврата

Код Что значит
0 всё хорошо (в том числе «новых версий нет»)
1 синхронизация не удалась (какое-то хранилище не выгрузилось)
2 ошибка в настройке или в аргументах команды
78 предполётная проверка контейнера не пройдена
130 остановлено оператором (docker stop, Ctrl+C)

Код 130 — это не авария. Он означает «попросили остановиться, остановились аккуратно». При этом текущая версия дописывается до конца, а следующие хранилища не запускаются. Следующий запуск продолжит с того же места.


9. Несколько контейнеров и одно хранилище

1С не даёт одному пользователю войти в хранилище дважды одновременно. Инструмент поэтому выстраивает обращения к хранилищу в очередь. Внутри одной машины это работает и между разными контейнерами — но только если выполнены три условия:

  1. общий том блокировок подключён ко всем контейнерам в одно и то же место (/var/lib/gitsync/sessions);
  2. все контейнеры запущены под одним UID;
  3. хранилище указано одинаково — один и тот же tcp://-адрес или один и тот же путь внутри контейнера. Два разных пути к одной папке инструмент свести не может и не пытается.

Проверено: два контейнера с общим томом заходят в хранилище строго по очереди (окна не пересекаются). Для контроля тот же опыт повторён с раздельными томами — там окна наложились на 2,7 секунды. То есть проверка действительно умеет отличать рабочую настройку от сломанной, а общий том — не украшение.

Если UID всё-таки разные. Файл блокировки создаётся с правами 0600, поэтому чужой UID не может ни взять блокировку, ни пройти мимо неё: обращение к хранилищу не состоится. Проверено на общем томе: контейнер-держатель с --user 10001:0, второй контейнер с --user 12345:0 и с --user 12345:12345 — оба получили отказ. Сообщение называет причину прямо, потому что ждать тут бессмысленно:

Нет прав на файл блокировки </var/lib/gitsync/sessions/….lock>: он принадлежит другому UID.
Ожидание тут не поможет — блокировка не занята, а недоступна.
Запускайте все обращения к одному хранилищу одной и той же парой UID:GID
(в контейнерах — одинаковый --user), либо дайте им разные каталоги сессий.

Граница этой проверки. Настоящий конфигуратор 1С в опыте очерёдности не запускался: подменён последний шов перед запуском процесса. Всё, что выше шва, — настоящий код. Проверка очерёдности на живой платформе остаётся невыполненной (§11).

Чего инструмент НЕ обещает. Блокировка действует в пределах одной машины и одной учётной записи. Между разными машинами, через сетевые диски, а также относительно конфигуратора, который кто-то открыл руками, никаких гарантий нет. На одно хранилище и один логин — одна машина-выгружатель.

Очередь допуска: одна лицензия на несколько заданий

Блокировка выше выстраивает в очередь ВХОД В ХРАНИЛИЩЕ. Лицензия — отдельный ресурс: платформа удерживает её на всё время работы конфигуратора, и на стенде два одновременных задания приводили к тому, что одному из них платформа отвечала «Не найдена лицензия. Не обнаружен ключ защиты программы или полученная программная лицензия!» (код 1). Порядок, кому откажут, случайный.

Штатный ответ на это — не обход механизма, а очередь допуска перед запуском контейнера: examples/gitsync-run.sh. Она берёт flock на файле в каталоге одного владельца (права 0700) и не пускает второе задание, пока идёт первое.

export GITSYNC_IMAGE=sha256:<идентификатор образа с платформой>
export GITSYNC_ADMISSION_DIR=/var/tmp/gitsync-admission
export GITSYNC_DOCKER_ARGS="--network gitsync_lm -u 10001:0 -e TZ=Europe/Moscow ..."
examples/gitsync-run.sh sync-all --config /work/monorepo.json --log-level INFO

Что это даёт и чего не даёт:

  • подача параллельная, исполнение последовательное. Два задания можно подать одновременно; оба дождутся своей очереди и завершатся штатно. Перекрытия работы конфигураторов при этом НЕТ и не обещается;
  • лицензионный механизм не меняется: ни ключи, ни nethasp.ini, ни сервер лицензий очередь не трогает и одноместность лицензии не доказывает;
  • ожидание ограничено GITSYNC_ADMISSION_WAIT (умолчание 1800 с). Превышение — код возврата 75: задание не запускалось и ничего не изменило;
  • код возврата задания проходит насквозь; SIGINT/SIGTERM останавливают контейнер (docker stop) и дают 130/143;
  • файлы замка НЕ удаляются никогда: удалить файл, на котором висит чужой flock, значит снять координацию — следующий процесс создаст файл заново и войдёт, пока предыдущий ещё работает;
  • каталог очереди с правами на запись для группы или всех — отказ (код 77): замок в таком каталоге ничего не гарантирует;
  • GITSYNC_SLOTS по умолчанию 1 — единственное подтверждённое значение. Повышать его стоит, только если ваша лицензия проверена на нескольких одновременных сеансах;
  • задания, запущенные мимо этого сценария (в том числе конфигуратор, открытый руками), в очереди не участвуют, и на них гарантия не распространяется.

10. Образ с платформой 1С (сборка, лицензирование и границы проверки)

Что здесь изменилось. Раздел больше не заготовка: приватный образ собран из настоящего комплекта 8.3.27.2130 x86_64 (setup-full-8.3.27.2130-x86_64.run) на Debian 12, платформа установлена и запускается. Команды ниже — те самые, что выполнялись, а не переписанные из документации.

Исторический отказ и текущий статус. Первое обращение на раннем стенде завершилось ошибкой «Не найдена лицензия», код 1. Позднее с действующим лицензированием выполнены реальные выгрузки файловых и серверных хранилищ. См. VERIFIED_STATUS.md и TCP-отчёт. Лицензирование вашей среды остаётся задачей её владельца; ниже сохранены сведения о диагностике штатных отказов платформы.

Лицензия в контейнере: где искать и как читать отказ

Разбиралось на 8.3.27.2130 в этом самом образе. Здесь только то, что подтверждено запусками.

Каталог, из которого платформа читает файл лицензии в Linux, — /var/1C/licenses. Проверено детектором чтения: копии одного и того же .lic разложены по всем правдоподобным каталогам, каждой сдвинуто время последнего доступа, после запуска конфигуратора время обновилось только у /var/1C/licenses/…. Каталоги $HOME/.1cv8/1C/1cv8 (и его conf), $HOME/.1cv8/conf, $HOME/.1C/1cv8/conf, /opt/1cv8/conf, /opt/1cv8/x86_64/<версия>/licenses платформа для этого не открывает вовсе. Каталог licenses внутри установки — это тексты лицензионного соглашения, а не хранилище лицензий.

docker run --rm ... -v /srv/1c-licenses:/var/1C/licenses:ro  gitsync-py:native-8.3.27.2130 ...

Файл лицензии нельзя просто скопировать с рабочего места. Программная лицензия привязана к параметрам компьютера, на котором её активировали. Пользователю платформа в любом случае говорит «Не найдена лицензия», а настоящая причина видна только в технологическом журнале:

LIC,1,...,Func=getLicense,res=error,txt='0, local Designer, error, local Designer;
  soft, file:///var/1C/licenses/20260914163343.lic, cached, binding error, long, ..., developer;
EXCP,...,Descr='...LicenseBaseImpl.cpp(8508):
  Ошибка программного лицензирования. Ошибка привязки программной лицензии к компьютеру:
  file:///var/1C/licenses/20260914163343.lic
  Sys Name_0: РАБОЧАЯ-СТАНЦИЯ      ← имя компьютера, записанное в лицензии
  Sys name_0: 7b1c0f3a9d42          ← имя этого контейнера

Так выглядит попытка отдать контейнеру лицензию, активированную на другой машине (в примере — на рабочей станции с Windows). Ни размещение файла, ни права доступа этого не лечат: контейнер — другой компьютер и другая ОС. Подгонять параметры машины под чужую привязку нельзя — это обход лицензирования, а не настройка.

Рабочие пути ровно два, и оба — к владельцу лицензии:

  • активировать программную лицензию внутри этого окружения (активация необратима и привязывается к нему же);
  • пробросить в контейнер аппаратный ключ защиты (или дать доступ к серверу лицензий по правилам вашей организации).

Как включить этот журнал. Каталог настроек платформы задан в /opt/1cv8/conf/conf.cfg (ConfLocation=/opt/1cv8/conf); положите туда logcfg.xml:

<config xmlns="http://v8.1c.ru/v8/tech-log">
    <log location="/work/tj" history="1">
        <event><eq property="name" value="LIC"/></event>
        <event><eq property="name" value="EXCP"/></event>
        <property name="all"/>
    </log>
</config>

Журнал ляжет в /work/tj/<процесс>/<дата><час>.log в UTF-8. Выключая диагностику, файл logcfg.xml уберите: журнал пишется постоянно.

Где вообще брать причину отказа конфигуратора. Конфигуратор 1С пишет диагностику в файл, переданный ключом /Out. В stdout/stderr при этом уходит постороннее (например, Fontconfig error: No writable cache directories), и по ним причина не восстанавливается. Инструмент читает файл /Out сам и показывает его текст в ошибке — вместе с путём к файлу, чтобы можно было посмотреть целиком:

Ошибка на версии 3: Конфигуратор завершился с кодом 1: /opt/1cv8/.../1cv8 DESIGNER ...
Сообщение конфигуратора (/var/lib/gitsync/scratch/native-.../designer-out/designer-out-….log):
Не найдена лицензия. Не обнаружен ключ защиты программы или полученная программная лицензия!

UID контейнера: платформе нужна учётная запись

Ядро образа рассчитано на запуск с произвольным UID. Толстый клиент 1С — нет: если текущего UID нет в /etc/passwd, 1cv8 завершается по SIGSEGV (код 139) ещё до создания файловой базы, без сообщения и без файла /Out. Проверено: --user 10001:0 — код 0; --user 12345:0 — код 139; тот же --user 12345:0 с подложенным /etc/passwd, где эта строка есть, — снова код 0.

Поэтому в приватном образе первым в ENTRYPOINT стоит docker/onec-user-entrypoint.sh: он дописывает запись о текущем UID в /etc/passwd (файл для этого принадлежит группе 0 и доступен ей на запись) и через exec передаёт управление дальше. Если /etc/passwd подключён только на чтение, обёртка не падает, а предупреждает — тогда запускайте контейнер с --user 10001:0.

Переопределяете HOME — дайте записываемый кэш

HOME в образе (/home/app) принадлежит app:0 и доступен на запись, в том числе при чужом UID из группы 0. Если вы задаёте свой HOME и он оказывается недоступен на запись, каждый запуск конфигуратора начинает сыпать Fontconfig error: No writable cache directories (по четыре строки на запуск). Работу это не останавливает, но засоряет диагностику. Лечится одной переменной:

docker run ... -e HOME=/мой/каталог -e XDG_CACHE_HOME=/tmp/fontcache ...

Что понадобится от вас

  1. Легально полученный дистрибутив толстого клиента 1С:Предприятие для Linux x86_64. Именно толстого: тонкий клиент разработку конфигураций не поддерживает, это сказано в документации платформы явно.
  2. Понимание, как он будет лицензирован в контейнере. Это отдельный вопрос, и наличие сервера лицензий в сети сам по себе ничего не доказывает. Программное лицензирование привязывается к параметрам машины, а контейнер создаётся заново каждый раз.
  3. Желательно — та же версия, что стоит на рабочих местах.

Дистрибутив кладите в отдельную папку, где нет ничего лишнего:

/srv/private/onec-8.3.27.2130/       ← только файлы вашего комплекта

Сборка

Нужен BuildKit (docker buildx): дистрибутив подключается на время сборки и не попадает ни в контекст, ни в слои образа.

docker buildx build -f docker/Dockerfile.native \
  --build-context onec=/srv/private/onec-8.3.27.2130 \
  --build-arg ONEC_VERSION=8.3.27.2130 \
  --build-arg CORE_IMAGE=gitsync-py:core \
  -t gitsync-py:native-8.3.27.2130 --load .

Файл установщика в каталоге комплекта должен быть исполняемым (chmod +x setup-full-*.run): он запускается прямо с подключённого каталога, копия в образ не делается.

Ключи установщика и состав комплекта

Комплект 8.3.27.2130 — это установщик BitRock. Его собственный --help перечисляет компоненты; выдуманных имён там нет, поэтому сверяйтесь с ним:

./setup-full-8.3.27.2130-x86_64.run --help

Рецепт ставит ровно то, что нужно для выгрузки хранилища:

--mode unattended --unattendedmodeui none
--enable-components  client_full,ru
--disable-components client_thin,client_thin_fib,v8_install_deps,desktop_icons,
                     server,ws,server_admin,config_storage_server,
                     additional_admin_functions,liberica_jre,integrity_monitoring,<прочие языки>
  • client_full — толстый клиент с конфигуратором; это он и нужен.
  • en компонентом не является: английский входит в поставку всегда, а попытка передать en — ошибка установщика («Неверное значение "en"»).
  • v8_install_deps отключён намеренно: зависимости уже поставлены пакетами дистрибутива, и пусть их ставит apt, а не установщик по своему списку.
  • server/ws/config_storage_server не нужны: файловое хранилище читает клиент.

Установка занимает около полуминуты и кладёт платформу в /opt/1cv8/x86_64/8.3.27.2130 (около 2.4 ГБ).

Системные библиотеки

Список получен ldd на установленном 1cv8, а не из документации. Толстый клиент 8.3.27 собран на wxWidgets/GTK3 и тянет WebKitGTK 4.0 (не 4.1) вместе с libsoup 2.4:

libgtk-3-0  libwebkit2gtk-4.0-37  libjavascriptcoregtk-4.0-18  libsoup2.4-1
libcups2  libgl1  libglu1-mesa  libsm6  libx11-6  libxxf86vm1  libgomp1
fontconfig  fonts-dejavu-core  iproute2  locales

Это прямо определяет базу образа: libwebkit2gtk-4.0-37 есть в Debian 12 (bookworm) и уже отсутствует в Debian 13 и Ubuntu 24.04. Поэтому база образа ядра закреплена на bookworm — при переезде на новую базу этот пункт надо решать заново, а не «поднимать версию».

iproute2 здесь не для красоты: платформа перечисляет сетевые интерфейсы вызовом /sbin/ip, и без пакета каждый запуск конфигуратора сыплет в stderr /bin/sh: 1: /sbin/ip: not found.

Сборка намеренно падает, если после установки нет исполняемого файла /opt/1cv8/x86_64/<версия>/1cv8 или если ldd показывает недостающие библиотеки.

Xvfb — нужен, это проверено

Конфигуратор не работает без X-сервера даже в пакетном режиме:

$ 1cv8 CREATEINFOBASE File=/tmp/ib /DisableStartupDialogs /DisableStartupMessages
*** Unable to initialize GTK+ or connect to the windowing system. Is DISPLAY set properly?
код возврата 255

Под Xvfb та же команда даёт код 0 и настоящий файл базы. /DisableStartupDialogs убирает диалоги, но зависимость от дисплея не снимает. Поэтому Xvfb в образе обязателен.

Но запускать его через xvfb-run нельзя. xvfb-run держит команду дочерним процессом (ему надо погасить X после завершения), и SIGTERM от docker stop достаётся посреднику, а не работе. Замерено на этом образе: контейнер останавливался мгновенно с кодом 143, обработчик в команде не срабатывал ни разу — то есть stop_grace_period: 70m из docker/compose.yaml не работал вообще, и синхронизация обрывалась бы на полуслове.

Поэтому в образе своя обёртка docker/xvfb-entrypoint.sh: она поднимает Xvfb (номер дисплея выбирает сам Xvfb через -displayfd, без гонки за номер) и запускает команду через exec. Посредника в цепочке не остаётся. После исправления тот же опыт даёт код 0 и сработавший обработчик.

X-сокет с хоста пробрасывать не надо, Wine использовать нельзя — это не «нативный Linux».

Манифест для настоящего хранилища

{
  "repository": "/repo",
  "defaults": {
    "backend": "native",
    "v8_path": "/opt/1cv8/x86_64/8.3.27.2130/1cv8",
    "jobs": 1,
    "queue_limit": 1,
    "storage_user": "gitsync",
    "storage_password_file": "/run/secrets/storage_password",
    "init": true
  },
  "storages": [
    { "name": "Конфигурация", "subtree": "Конфигурация",
      "storage_path": "tcp://сервер-хранилища/база" },
    { "name": "Расширение печати", "subtree": "Расширение печати",
      "extension": "РасширениеПечатныхФорм",
      "storage_path": "tcp://сервер-хранилища/база-печать" },
    { "name": "Расширение обменов", "subtree": "Расширение обменов",
      "extension": "РасширениеОбменов",
      "storage_path": "tcp://сервер-хранилища/база-обмены" }
  ]
}

Про схему «основная конфигурация + N расширений» подробно написано в docs/monorepo.md. Коротко: один репозиторий, по подкаталогу на источник, у каждого свои VERSION и AUTHORS, один .git на всех.

Начинайте с jobs: 1, queue_limit: 1. Значения по умолчанию (4/4) для первого запуска слишком смелые: каждый поток — это отдельная временная база 1С.

Имя расширения в ключе extension — это имя в 1С, а не имя подкаталога. Они могут не совпадать.

Как подключить хранилище

  • Лучше всего — сервер хранилища (tcp://…). Тогда файлы хранилища остаются там, где были, и раздавать файловый доступ не нужно.
  • Файловое хранилище подключается как папка (/storage/main). Сетевые пути Windows (\\сервер\папка) внутри контейнера не работают: их надо сначала подключить на хосте, а уже потом отдавать контейнеру.
  • Подключать хранилище только для чтения — надо проверить. Сам инструмент хранилище не меняет: ни захвата версий, ни фиксации, ни привязки базы он не делает. Но платформа при файловом доступе может писать служебные файлы, и на Linux это не проверялось. Порядок действий: сделайте копию хранилища, подключите её только для чтения, посмотрите, проходит ли отчёт по версиям и выгрузка. Если не проходит — оставьте запись, но работайте на копии. Молча выдавать право записи на рабочее хранилище «чтобы проверить» нельзя.

Кириллица, кодировки, шрифты

Образ собран с LANG=C.UTF-8, имена файлов в UTF-8 — проверено, кириллические имена каталогов выгрузки работают. Для платформы дополнительно ставится fontconfig и базовый набор шрифтов: документация 1С называет их обязательными для части операций с табличными документами, а отчёт по версиям хранилища — это как раз табличный документ.


11. Что осталось проверить на настоящей платформе

Пока не закрыты пункты 4–7 и 9, нельзя говорить, что выгрузка из хранилища 1С в Linux работает. Список — по порядку, с текущим состоянием.

  1. ✅ Сделано. Приватный образ собран из настоящего комплекта; 1cv8 — линуксовый ELF x86-64, ldd без пропаж, кириллица и UTF-8 в порядке.
  2. ⛔ Упёрлось в лицензию, и причина теперь названа точно. Контейнер лицензии не получает: на первом обращении к хранилищу платформа отвечает «Не найдена лицензия. Не обнаружен ключ защиты программы или полученная программная лицензия!» (код 1). Разобрано в §10: файл лицензии платформа читает из /var/1C/licenses, а отвергает его по привязке к другому компьютеру («Ошибка привязки программной лицензии к компьютеру» в технологическом журнале). Пригодна только лицензия, полученная для этого окружения. Вопросы «хватает ли лицензии при jobs: 2» и «не слетает ли она при пересоздании контейнера» остаются открытыми — до них дело не дошло.
  3. ✅ Сделано. CREATEINFOBASE под Xvfb проходит с кодом 0 и создаёт настоящий файл базы (1Cv8.1CD). Лицензия для этого не требуется.
  4. ⛔ Отчёт по версиям настоящего хранилища — не получен (лицензия).
  5. ⛔ Выгрузка версии ConfigurationRepositoryDumpCfg -v N → LoadCfg → DumpConfigToFiles — не выполнялась (лицензия). Это по-прежнему три отдельных запуска, объединять их нельзя.
  6. ⛔ Прогон основной конфигурации и двух разных расширений на настоящих хранилищах — не выполнялся (лицензия). Конвейер до платформы доведён: CLI дошёл до запуска конфигуратора по всем трём источникам и честно остановился на отказе, не тронув соседей и не сделав ни одного повтора.
  7. ⛔ Сверка с независимой ручной выгрузкой — не выполнялась (лицензия).
  8. ✅ Сделано, и нашлась поломка. docker stop через xvfb-run до работы не доходил (код 143, обработчик не срабатывал). Исправлено собственной обёрткой с exec; после исправления сигнал доходит, код 0. Xvfb нужен — без него конфигуратор не стартует вовсе.
  9. ⛔ Сравнение с прогоном на Windows той же версии — не выполнялось (лицензия).

Пункты 4–7 и 9 нельзя закрыть ничем, кроме настоящей лицензии. Учебная фикстура их не заменяет и не может.


12. Куда смотреть дальше