# Symfony Lock на Linux/VPS

## Один сервер, отдельный пользователь

Положите код вне web-root, например /opt/report-app. PHP CLI и FPM могут читать разные php.ini; проверьте `php --ini` и `php -m`. Установите зависимости через `composer install --no-dev --no-interaction --no-plugins --prefer-dist`. Не используйте composer update во время обычного развёртывания.

```sh
sudo useradd --system --home /var/lib/report-app --shell /usr/sbin/nologin report-worker
sudo install -d -o report-worker -g report-worker -m 0700 /var/lib/report-app/locks
sudo -u report-worker env LOCK_DIR=/var/lib/report-app/locks php /opt/report-app/examples/report.php
```

Создание пользователя — одноразовая операция: если уже существует, переиспользуйте подходящую служебную учётную запись. Код должен читаться этой учётной записью, но не быть доступным ей на запись без необходимости. Не давайте 777 на общий каталог.

В examples/report.php ключ myapp:prod:daily-report одинаковый для ручного запуска и планировщика. Каталог LOCK_DIR также одинаковый. Файл .lock может оставаться после завершения; это не означает занятый lock. Не удаляйте файл, пока рядом могут работать владельцы.

## systemd timer либо cron

Unit в deploy/report.service запускает конкретный PHP, задаёт рабочий каталог и env, использует фиксированного пользователя и ReadWritePaths только для состояния. Unit не содержит общих секретов. Сначала выполните:

```sh
sudo cp deploy/report.service deploy/report.timer /etc/systemd/system/
sudo systemd-analyze verify /etc/systemd/system/report.service /etc/systemd/system/report.timer
sudo systemctl daemon-reload
sudo systemctl start report.service
sudo journalctl -u report.service -n 50 --no-pager
sudo systemctl enable --now report.timer
systemctl list-timers report.timer
```

Замените пути и пользователя своим окружением. Альтернатива — строка из deploy/report.cron. Не включайте оба планировщика по ошибке. Сам timer предотвращает параллельный запуск активного того же service unit, но Lock полезен для ручных запусков, других unit и приложений, обращающихся к тому же ресурсу.

## Несколько серверов

У всех worker должен быть один доступный RedisStore и согласованные ключи среды/приложения/ресурса. Локальный Flock разных хостов не является общим. Redis должен находиться в приватной сети с ограниченным доступом, нужной аутентификацией и TLS там, где это требуется. Не публикуйте 6379 в Интернет. Используйте отдельные данные подключения, права на нужные команды и ключи, таймауты соединения/чтения.

Пример Stores.php использует Predis. LOCK_REDIS_PASSWORD читается из окружения и не сохраняется в recording.json. Для systemd можно использовать закрытый EnvironmentFile вне web-root; переменные окружения — способ передачи, а не самостоятельное шифрование. При TLS примените схему tls и настройки проверки сертификата по документации Predis; не отключайте verify_peer ради успешного теста.

Не отправляйте lock-ключи в Redis с политикой вытеснения кеша: исчезновение живого ключа меняет гарантию. Failover/восстановление данных и смену store проверяйте отдельно. Один Redis лаборатории не доказывает корректность кластера при сбое.

## TTL и сохранение результата

Выбирайте TTL с запасом для порции работы и наблюдаемых пауз. Refresh делайте до истечения. При exception/потере владения прерывайте дальнейшие побочные эффекты, записывайте диагностический идентификатор, а повтор запускайте только с учётом идемпотентности. Проверка isAcquired сама по себе оставляет окно до записи; строгий ресурс должен отвергать устаревший fencing token или выполнять подходящую атомарную проверку версии.

Финансовые/необратимые операции требуют гарантий на уровне БД/внешнего API. Lock уменьшает конкурентный конфликт, но не превращает сетевой POST в exactly-once. Срок хранения idempotency key и уникальные ограничения проектируются отдельно.

## Наблюдение и обновление

Логируйте ключ или его безопасную производную, результат acquired/skipped/lost, время ожидания, длительность владения, исключение store и ID задачи. Не пишите Redis-пароль, полный DSN или приватные бизнес-данные. Следите за ростом skipped, timeouts и refresh failures.

Перед сменой имени ключа или backend остановите/дождитесь старых worker: новый и старый store не координируются. Сохраните предыдущий composer.lock, проверьте обновление на своих конкурентных сценариях, затем раскатывайте. Лаборатория выполнялась локально в Windows и Linux-контейнере; ваши DNS, systemd, права и Redis на удалённом VPS требуют отдельной проверки.
