# XML-интеграция на Linux/VPS

У мастерской два разных способа применения. Готовый public — статические файлы сайта: браузер выполняет библиотеку сам. Node CLI — обработка файла на сервере, которую можно включить в импорт по расписанию. Ни один вариант не требует публичного сервера с произвольным запуском кода пользователя.

## Статическая мастерская

Соберите public в CI или локально проверенной версией Node, затем перенесите только его содержимое в каталог сайта. Например, в /var/www/xml-workshop. Пользователь Nginx должен иметь право чтения; запись ему не нужна. Node, исходники, node_modules, файлы поставщиков и ключи в document root не размещайте.

Если используете Nginx, отдельный location может выглядеть так. Это фрагмент существующего server-блока, а не команда замены всего системного конфига.

```nginx
location /xml-workshop/ {
    alias /var/www/xml-workshop/;
    index index.html;
    add_header X-Content-Type-Options nosniff always;
}
```

Включённый стандартный mime.types должен отдавать .js как JavaScript, .css как CSS, .json как JSON. Перед применением проверьте nginx -t и только затем выполните reload. HTTPS настраивается на существующем виртуальном хосте. Если сайт использует CSP, для мастерской нужны script-src 'self' и worker-src 'self', а также чтение локального sources.json. В iframe учитывайте CSP родительской страницы и разрешение скриптов. Не отключайте политику целиком ради одного Worker.

## CLI под отдельным пользователем

Пример рассчитан на systemd и Node 24, установленный в /usr/bin/node. На конкретном VPS сначала выполните command -v node и укажите фактический абсолютный путь. Способ установки Node выбирайте по дистрибутиву и официальным инструкциям, не копируйте случайную команду curl|sh.

```bash
sudo useradd --system --home /opt/xml-import --shell /usr/sbin/nologin xml-import
sudo install -d -o root -g xml-import -m 0750 /opt/xml-import
sudo install -d -o xml-import -g xml-import -m 0750 /var/lib/xml-import/incoming
```

Скопируйте package.json, package-lock.json, src и tools/cli.mjs в /opt/xml-import. Установите production-зависимости при развёртывании: npm ci --ignore-scripts --omit=dev --no-audit --no-fund. Дайте сервису чтение проекта, без права изменения исходников. Файл поставщика разместите как /var/lib/xml-import/incoming/catalog.xml с владельцем/группой, доступными сервису. Права каталога и файла — отдельные условия: одного chmod файла недостаточно, если родительский каталог закрыт.

```ini
# /etc/systemd/system/xml-import.service
[Unit]
Description=Validate supplier XML into a normalized result
After=local-fs.target

[Service]
Type=oneshot
User=xml-import
Group=xml-import
WorkingDirectory=/opt/xml-import
ExecStart=/usr/bin/node /opt/xml-import/tools/cli.mjs contract /var/lib/xml-import/incoming/catalog.xml
TimeoutStartSec=15
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
UMask=0077
```

```bash
sudo systemctl daemon-reload
sudo systemctl start xml-import.service
sudo systemctl status xml-import.service
sudo journalctl -u xml-import.service -n 50 --no-pager
```

Этот unit **проверяет и печатает результат**, не выполняет запись в CRM. Код 2 от CLI означает неуспешное завершение unit. Успех нельзя подменить проглатыванием исключения. Для реальной интеграции добавьте отдельный этап применения проверенных строк: транзакцию, уникальный внешний ID, журнал запуска и повторяемость импорта. Не отправляйте частично проверенные rows в базу: наш контракт при любой ошибке возвращает пустой массив rows вместе со списком проблем.

## Расписание и передача файла

Поставщик или отдельный загрузчик должен сначала записать новый файл во временное имя, завершить запись и атомарно переименовать его в catalog.xml в пределах одной файловой системы. Так обработчик не увидит недописанный документ. Пример timer не загружает файл из сети; загрузка, аутентификация поставщика и повтор доставки остаются отдельным процессом.

```ini
# /etc/systemd/system/xml-import.timer
[Unit]
Description=Check supplier XML every hour
[Timer]
OnCalendar=hourly
Persistent=true
[Install]
WantedBy=timers.target
```

```bash
sudo systemctl enable --now xml-import.timer
systemctl list-timers xml-import.timer
```

Идемпотентность требуется даже при timer: один и тот же файл может быть проверен повторно. Запоминайте контрольную сумму файла/идентификатор партии и результат применения в базе. Загрузка обновлённого каталога, его проверка и применение — три разных события, каждое должно иметь наблюдаемый статус.

## Ограничения и диагностика

Учебный CLI принимает только локальные UTF-8 файлы до 32 KiB, читает размер до readFile, затем ещё раз проверяет фактические байты в обработчике. Изменение файла между stat и readFile может изменить прочитанный размер: потому повторная проверка обязательна. Такой путь не подходит для многогигабайтного каталога. Для него нужен потоковый parser и пакетная транзакционная обработка с измерением памяти.

Не журналируйте полный коммерческий каталог или персональные данные без необходимости. В демонстрации stdout содержит исходные поля для обучения; в production выводите ID партии, количество записей и пути ошибок, а полные документы храните отдельно с ограниченным доступом и сроком хранения. Наличие корректного XML не подтверждает полномочия отправителя, допустимость цены или принадлежность клиента.

Режим процесса Worker ограничивает ожидание и сохраняет основной поток отзывчивым, но не заменяет лимиты ресурсов ОС, размер HTTP body и квоты по пользователю. Если переносите адаптер в API, ограничьте вход **до** чтения всего body, добавьте авторизацию и rate limit. Нельзя предоставлять пользователю путь к серверному файлу напрямую. В этом архиве публичного HTTP endpoint импорта нет.

Linux-проверки проекта выполняются локально в контейнере. Эти инструкции для VPS — воспроизводимый образец конфигурации, а не утверждение о развёртывании вашего сервера.
