cron не может запустить .sh скрипт – 22.04

Вопрос или проблема

Я пытаюсь запустить простой скрипт, который проверяет процент заряда батареи и показывает его через уведомление (notify-send).
Скрипт корректно выполняется, если его запускать вручную из терминала: ~/path-to-script/my_script.sh или sh ~/path-to-script/my_script.sh или даже bash ~/path-to-my-script/my_script.sh.
При этом уведомление отображается корректно.

Однако при попытке запустить его через cron уведомление не появляется.
Вот как выглядит crontab -e:

*/1 * * * * /usr/bin/sh ~/path-to-my-script/my_script.sh

Я также задал некоторые переменные окружения в crontab с помощью sudo vim /etc/crontab:

SHELL=/usr/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

И вот лог из sudo service cron status:

gen 28 17:35:01 my_user CRON[7863]: pam_unix(cron:session): session opened for user my_user(uid=1001) by (uid=0)
gen 28 17:35:01 my_user CRON[7864]: (my_user) CMD (/usr/bin/sh ~/path-to-my-script/my_script.sh)
gen 28 17:35:01 my_user CRON[7863]: (CRON) info (No MTA installed, discarding output)
gen 28 17:35:01 my_user CRON[7863]: pam_unix(cron:session): session closed for user my_user

Я использую Ubuntu 22.04 с i3 как оконный менеджер.
В чем может быть причина этой проблемы?

Пожалуйста, спрашивайте, если нужна дополнительная информация.

Современный и чистый способ сделать это – использовать таймеры systemd пользователя. Отсутствие переменной среды DBus – это только одна из многих причин, почему cron устарел на современной Ubuntu Linux.

  1. Вставьте это в ~/.config/systemd/user/battery_notification.service:
[Unit]
Description=Sends notification about battery status

[Service]
# %h означает ~
ExecStart=%h/path-to-my-script/my_script.sh
Type=oneshot
  1. Вставьте это в ~/.config/systemd/user/battery_notification.timer:
[Unit]
Description=Show battery notification every minute

# Если вы хотите ограничить его определенной средой рабочего стола,
# вы можете изменить эту строку.
# Это в основном предотвращает ненужные запуски в SSH или после выхода из системы,
# и является еще одним преимуществом перед cron.
# РЕДАКТИРОВАТЬ: К сожалению, в i3 существует ошибка, которая это ломает: https://github.com/i3/i3/issues/5186
#Requisite=graphical-session.target
#PartOf=graphical-session.target

[Install]
#WantedBy=graphical-session.target
WantedBy=dbus.service

[Timer]
AccuracySec=1us
# Начальная задержка
OnActiveSec=1s
# Период
OnUnitActiveSec=1m
  1. Выполните следующее:
systemctl --user daemon-reload
systemctl --user enable battery_notification.timer
systemctl --user start battery_notification.timer # Или перезагрузите систему

Вы увидите, что переменная среды DBus, а также многие другие отсутствующие переменные среды установлены правильно. Программа теперь является дочерним процессом одного из ваших пользовательских процессов, а именно systemd --user, а не cron, поэтому она находится в правильной cgroup и других пространствах имен.

Еще одним преимуществом является то, что она прекращает работу после выхода из системы или если вы не вошли в вашу указанную графическую среду. Полезными командами могут быть systemctl --user list-timers для проверки оставшегося времени или systemctl --user disable battery_notification.timer && systemctl --user stop battery_notification.timer для остановки.

Как указано в моем комментарии, решение оказалось таким: добавление env DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus к команде в crontab -e следующим образом:

*/1 * * * * env DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus /usr/bin/sh ~/path-to-my-script/my_script.sh

Переименуйте ваш файл из my_script.sh в просто my_script, тогда он будет выполнен cron.

.

Ответ или решение

Теория

Задача заключается в настройке автоматического запуска скрипта на Ubuntu 22.04, который выводит уведомление о текущем уровне заряда батареи, используя notify-send. Скрипт успешно выполняется вручную из терминала, однако при попытке запуска с помощью cron, уведомления не появляются. Это типичная проблема, связанная с ограничениями cron на взаимодействие с графической средой пользователя.

При запуске графических приложений или отправке уведомлений в среде X Window, таких как notify-send, требуется доступ к графическим переменным среды, таким как DISPLAY и DBUS_SESSION_BUS_ADDRESS. По умолчанию, cron не передает эти переменные окружения, что и является основной причиной отказа сценария.

Пример

В логах службы cron видно, что попытка запуска скрипта завершается без сообщения об ошибке, просто потому, что cron отбрасывает выходные данные, отсутствует MTA для отправки почты, а уведомления не появляются из-за неверного наличия переменных среды.

Изучим два способа решения этой проблемы: корректное назначение переменных окружения в crontab и использование инструментов systemd timers.

Применение

  1. Настройка переменных окружения в crontab:

    Необходимо добавить в crontab переменные окружения, которые поддерживают работу с X-сессиями. Например, DBUS_SESSION_BUS_ADDRESS может быть получен с помощью команды:

    echo $(dbus-launch | grep DBUS_SESSION_BUS_ADDRESS)

    Затем вы можете добавить скорректированную строку в crontab, чтобы переменная была передана вашему скрипту:

    */1 * * * * env DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus /usr/bin/sh ~/path-to-my-script/my_script.sh

    Команда DISPLAY=:0 может потребоваться только если у вашего пользователя запущено несколько X-серверов. В оригинальной строке использован вызов sh, который может не учитывать все инструменты, стоящие в распоряжении bash. Убедитесь, что ваш скрипт начинается с shebang #!/bin/bash и не требует дополнительных оболочек для выполнения.

  2. Использование systemd timers:

    В современных системах, таких как Ubuntu 22.04, systemd предоставляет более точное и гибкое средство для выполнения задач по расписанию. Вот как это можно сделать:

    1. Создание сервиса:

      Создайте файл ~/.config/systemd/user/battery_notification.service с содержимым:

      [Unit]
      Description=Sends notification about battery status
      
      [Service]
      ExecStart=%h/path-to-my-script/my_script.sh
      Type=oneshot
    2. Создание таймера:

      Создайте файл ~/.config/systemd/user/battery_notification.timer:

      [Unit]
      Description=Show battery notification every minute
      
      [Install]
      WantedBy=default.target
      
      [Timer]
      AccuracySec=1us
      OnUnitActiveSec=1m
    3. Активация:

      Выполните команды:

      systemctl --user daemon-reload
      systemctl --user enable battery_notification.timer
      systemctl --user start battery_notification.timer

С помощью данных инструментов можно гарантировать, что ваш скрипт будет работать корректно и стабильно, используя все необходимые ресурсы и настройки графической среды Ubuntu. Выбор между cron и systemd timers зависит от ваших предпочтений и требований к кроссплатформенности решения.

Не забывайте также проверять права доступа на скрипт и выполнять его от имени пользователя, обладающего достаточными правами, чтобы выполнять команды и отправлять уведомления.

Оцените материал
Добавить комментарий

Капча загружается...